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
168 lines
4.0 KiB
Go
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")
|
|
}
|
|
}
|