Files
silo/internal/partnum/generator_test.go
Forbes 1f7960db50 feat: implement date segment type for part number generation
Fixes #79

Implement the date segment type in the part number generator. Uses Go's
time.Format with the segment's Value field as the layout string.

- Default format: 20060102 (YYYYMMDD) when no Value is specified
- Custom formats via Value field: "0601" (YYMM), "2006" (YYYY), etc.
- Always uses UTC time
- Add 3 tests: default format, custom YYMM format, year-only format
2026-02-13 13:10:57 -06:00

254 lines
6.1 KiB
Go

package partnum
import (
"context"
"fmt"
"regexp"
"testing"
"time"
"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")
}
}
func TestGenerateDateSegmentDefault(t *testing.T) {
s := &schema.Schema{
Name: "date-test",
Version: 1,
Separator: "-",
Segments: []schema.Segment{
{Name: "date", Type: "date"},
{Name: "serial", Type: "serial", Length: 3},
},
}
gen := NewGenerator(map[string]*schema.Schema{"date-test": s}, &mockSeqStore{})
pn, err := gen.Generate(context.Background(), Input{
SchemaName: "date-test",
Values: map[string]string{},
})
if err != nil {
t.Fatalf("Generate returned error: %v", err)
}
// Default format: YYYYMMDD-NNN
want := time.Now().UTC().Format("20060102") + "-001"
if pn != want {
t.Errorf("got %q, want %q", pn, want)
}
}
func TestGenerateDateSegmentCustomFormat(t *testing.T) {
s := &schema.Schema{
Name: "date-custom",
Version: 1,
Separator: "-",
Segments: []schema.Segment{
{Name: "date", Type: "date", Value: "0601"},
{Name: "serial", Type: "serial", Length: 4},
},
}
gen := NewGenerator(map[string]*schema.Schema{"date-custom": s}, &mockSeqStore{})
pn, err := gen.Generate(context.Background(), Input{
SchemaName: "date-custom",
Values: map[string]string{},
})
if err != nil {
t.Fatalf("Generate returned error: %v", err)
}
// Format "0601" produces YYMM
if matched, _ := regexp.MatchString(`^\d{4}-\d{4}$`, pn); !matched {
t.Errorf("got %q, want pattern YYMM-NNNN", pn)
}
want := time.Now().UTC().Format("0601") + "-0001"
if pn != want {
t.Errorf("got %q, want %q", pn, want)
}
}
func TestGenerateDateSegmentYearOnly(t *testing.T) {
s := &schema.Schema{
Name: "date-year",
Version: 1,
Separator: "-",
Segments: []schema.Segment{
{Name: "year", Type: "date", Value: "2006"},
{Name: "serial", Type: "serial", Length: 4},
},
}
gen := NewGenerator(map[string]*schema.Schema{"date-year": s}, &mockSeqStore{})
pn, err := gen.Generate(context.Background(), Input{
SchemaName: "date-year",
Values: map[string]string{},
})
if err != nil {
t.Fatalf("Generate returned error: %v", err)
}
want := time.Now().UTC().Format("2006") + "-0001"
if pn != want {
t.Errorf("got %q, want %q", pn, want)
}
}