Files
silo/internal/partnum/generator_test.go
Forbes d08b178466 test: add comprehensive test suite for backend
Add 56 tests covering the core backend packages:

Unit tests (no database required):
- internal/partnum: 7 tests for part number generation logic
  (sequence, format templates, enum validation, constants)
- internal/schema: 8 tests for YAML schema loading, property
  merging, validation, and default application

Integration tests (require TEST_DATABASE_URL):
- internal/db/items: 10 tests for item CRUD, archive/unarchive,
  revisions, and thumbnail operations
- internal/db/relationships: 10 tests for BOM CRUD, cycle detection,
  self-reference blocking, where-used, expanded/flat BOM
- internal/db/projects: 5 tests for project CRUD and item association
- internal/api/bom_handlers: 6 HTTP handler tests for BOM endpoints
  including flat BOM, cost calculation, add/delete entries
- internal/api/items: 5 HTTP handler tests for item CRUD endpoints

Infrastructure:
- internal/testutil: shared helpers for test DB pool setup,
  migration runner, and table truncation
- internal/db/helpers_test.go: DB wrapper for integration tests
- internal/db/db.go: add NewFromPool constructor
- Makefile: add test-integration target with default DSN

Integration tests skip gracefully when TEST_DATABASE_URL is unset.
Dev-mode auth (nil authConfig) used for API handler tests.

Fixes: fmt.Errorf Go vet warning in partnum/generator.go

Closes #2
2026-02-07 01:57:10 -06:00

168 lines
4.0 KiB
Go

package partnum
import (
"context"
"fmt"
"testing"
"github.com/kindredsystems/silo/internal/schema"
)
// mockSeqStore implements SequenceStore for testing.
type mockSeqStore struct {
counter int
}
func (m *mockSeqStore) NextValue(_ context.Context, _ string, _ string) (int, error) {
m.counter++
return m.counter, nil
}
func testSchema() *schema.Schema {
return &schema.Schema{
Name: "test",
Version: 1,
Separator: "-",
Segments: []schema.Segment{
{
Name: "category",
Type: "enum",
Required: true,
Values: map[string]string{
"F01": "Fasteners",
"R01": "Resistors",
},
},
{
Name: "serial",
Type: "serial",
Length: 4,
Scope: "{category}",
},
},
}
}
func TestGenerateBasic(t *testing.T) {
s := testSchema()
gen := NewGenerator(map[string]*schema.Schema{"test": s}, &mockSeqStore{})
pn, err := gen.Generate(context.Background(), Input{
SchemaName: "test",
Values: map[string]string{"category": "F01"},
})
if err != nil {
t.Fatalf("Generate returned error: %v", err)
}
if pn != "F01-0001" {
t.Errorf("got %q, want %q", pn, "F01-0001")
}
}
func TestGenerateSequentialNumbers(t *testing.T) {
s := testSchema()
seq := &mockSeqStore{}
gen := NewGenerator(map[string]*schema.Schema{"test": s}, seq)
for i := 1; i <= 3; i++ {
pn, err := gen.Generate(context.Background(), Input{
SchemaName: "test",
Values: map[string]string{"category": "F01"},
})
if err != nil {
t.Fatalf("Generate #%d returned error: %v", i, err)
}
want := fmt.Sprintf("F01-%04d", i)
if pn != want {
t.Errorf("Generate #%d: got %q, want %q", i, pn, want)
}
}
}
func TestGenerateWithFormat(t *testing.T) {
s := &schema.Schema{
Name: "formatted",
Version: 1,
Format: "{prefix}/{category}-{serial}",
Segments: []schema.Segment{
{Name: "prefix", Type: "constant", Value: "KS"},
{Name: "category", Type: "enum", Required: true, Values: map[string]string{"A": "Alpha"}},
{Name: "serial", Type: "serial", Length: 3},
},
}
gen := NewGenerator(map[string]*schema.Schema{"formatted": s}, &mockSeqStore{})
pn, err := gen.Generate(context.Background(), Input{
SchemaName: "formatted",
Values: map[string]string{"category": "A"},
})
if err != nil {
t.Fatalf("Generate returned error: %v", err)
}
if pn != "KS/A-001" {
t.Errorf("got %q, want %q", pn, "KS/A-001")
}
}
func TestGenerateUnknownSchema(t *testing.T) {
gen := NewGenerator(map[string]*schema.Schema{}, &mockSeqStore{})
_, err := gen.Generate(context.Background(), Input{
SchemaName: "nonexistent",
Values: map[string]string{},
})
if err == nil {
t.Fatal("expected error for unknown schema, got nil")
}
}
func TestGenerateMissingRequiredEnum(t *testing.T) {
s := testSchema()
gen := NewGenerator(map[string]*schema.Schema{"test": s}, &mockSeqStore{})
_, err := gen.Generate(context.Background(), Input{
SchemaName: "test",
Values: map[string]string{}, // missing required "category"
})
if err == nil {
t.Fatal("expected error for missing required enum, got nil")
}
}
func TestGenerateInvalidEnumValue(t *testing.T) {
s := testSchema()
gen := NewGenerator(map[string]*schema.Schema{"test": s}, &mockSeqStore{})
_, err := gen.Generate(context.Background(), Input{
SchemaName: "test",
Values: map[string]string{"category": "INVALID"},
})
if err == nil {
t.Fatal("expected error for invalid enum value, got nil")
}
}
func TestGenerateConstantSegment(t *testing.T) {
s := &schema.Schema{
Name: "const-test",
Version: 1,
Separator: "-",
Segments: []schema.Segment{
{Name: "prefix", Type: "constant", Value: "KS"},
{Name: "serial", Type: "serial", Length: 4},
},
}
gen := NewGenerator(map[string]*schema.Schema{"const-test": s}, &mockSeqStore{})
pn, err := gen.Generate(context.Background(), Input{
SchemaName: "const-test",
Values: map[string]string{},
})
if err != nil {
t.Fatalf("Generate returned error: %v", err)
}
if pn != "KS-0001" {
t.Errorf("got %q, want %q", pn, "KS-0001")
}
}