package db import ( "context" "fmt" "testing" ) func TestItemCreate(t *testing.T) { database := mustConnectTestDB(t) repo := NewItemRepository(database) ctx := context.Background() item := &Item{ PartNumber: "TEST-0001", ItemType: "part", Description: "Test item", } err := repo.Create(ctx, item, map[string]any{"color": "red"}) if err != nil { t.Fatalf("Create: %v", err) } if item.ID == "" { t.Error("expected item ID to be set") } if item.CurrentRevision != 1 { t.Errorf("current revision: got %d, want 1", item.CurrentRevision) } } func TestItemGetByPartNumber(t *testing.T) { database := mustConnectTestDB(t) repo := NewItemRepository(database) ctx := context.Background() item := &Item{PartNumber: "GET-PN-001", ItemType: "part", Description: "get by pn test"} if err := repo.Create(ctx, item, nil); err != nil { t.Fatalf("Create: %v", err) } got, err := repo.GetByPartNumber(ctx, "GET-PN-001") if err != nil { t.Fatalf("GetByPartNumber: %v", err) } if got == nil { t.Fatal("expected item, got nil") } if got.Description != "get by pn test" { t.Errorf("description: got %q, want %q", got.Description, "get by pn test") } // Non-existent should return nil, not error missing, err := repo.GetByPartNumber(ctx, "DOES-NOT-EXIST") if err != nil { t.Fatalf("GetByPartNumber (missing): %v", err) } if missing != nil { t.Error("expected nil for missing item") } } func TestItemGetByID(t *testing.T) { database := mustConnectTestDB(t) repo := NewItemRepository(database) ctx := context.Background() item := &Item{PartNumber: "GET-ID-001", ItemType: "assembly", Description: "get by id"} if err := repo.Create(ctx, item, nil); err != nil { t.Fatalf("Create: %v", err) } got, err := repo.GetByID(ctx, item.ID) if err != nil { t.Fatalf("GetByID: %v", err) } if got == nil { t.Fatal("expected item, got nil") } if got.PartNumber != "GET-ID-001" { t.Errorf("part_number: got %q, want %q", got.PartNumber, "GET-ID-001") } } func TestItemList(t *testing.T) { database := mustConnectTestDB(t) repo := NewItemRepository(database) ctx := context.Background() for i := 0; i < 3; i++ { item := &Item{ PartNumber: fmt.Sprintf("LIST-%04d", i), ItemType: "part", Description: fmt.Sprintf("list item %d", i), } if err := repo.Create(ctx, item, nil); err != nil { t.Fatalf("Create #%d: %v", i, err) } } items, err := repo.List(ctx, ListOptions{}) if err != nil { t.Fatalf("List: %v", err) } if len(items) != 3 { t.Errorf("expected 3 items, got %d", len(items)) } } func TestItemListByType(t *testing.T) { database := mustConnectTestDB(t) repo := NewItemRepository(database) ctx := context.Background() repo.Create(ctx, &Item{PartNumber: "TYPE-P-001", ItemType: "part", Description: "a part"}, nil) repo.Create(ctx, &Item{PartNumber: "TYPE-A-001", ItemType: "assembly", Description: "an assembly"}, nil) repo.Create(ctx, &Item{PartNumber: "TYPE-P-002", ItemType: "part", Description: "another part"}, nil) items, err := repo.List(ctx, ListOptions{ItemType: "part"}) if err != nil { t.Fatalf("List: %v", err) } if len(items) != 2 { t.Errorf("expected 2 parts, got %d", len(items)) } } func TestItemUpdate(t *testing.T) { database := mustConnectTestDB(t) repo := NewItemRepository(database) ctx := context.Background() item := &Item{PartNumber: "UPD-001", ItemType: "part", Description: "original"} if err := repo.Create(ctx, item, nil); err != nil { t.Fatalf("Create: %v", err) } err := repo.Update(ctx, item.ID, UpdateItemFields{ PartNumber: "UPD-001", ItemType: "part", Description: "updated", }) if err != nil { t.Fatalf("Update: %v", err) } got, _ := repo.GetByID(ctx, item.ID) if got.Description != "updated" { t.Errorf("description: got %q, want %q", got.Description, "updated") } } func TestItemArchiveUnarchive(t *testing.T) { database := mustConnectTestDB(t) repo := NewItemRepository(database) ctx := context.Background() item := &Item{PartNumber: "ARC-001", ItemType: "part", Description: "archivable"} if err := repo.Create(ctx, item, nil); err != nil { t.Fatalf("Create: %v", err) } // Archive if err := repo.Archive(ctx, item.ID); err != nil { t.Fatalf("Archive: %v", err) } // Should not appear in GetByPartNumber (excludes archived) got, _ := repo.GetByPartNumber(ctx, "ARC-001") if got != nil { t.Error("archived item should not be returned by GetByPartNumber") } // But should still be accessible by ID gotByID, _ := repo.GetByID(ctx, item.ID) if gotByID == nil { t.Fatal("archived item should still be accessible by GetByID") } if gotByID.ArchivedAt == nil { t.Error("archived_at should be set") } // Unarchive if err := repo.Unarchive(ctx, item.ID); err != nil { t.Fatalf("Unarchive: %v", err) } got, _ = repo.GetByPartNumber(ctx, "ARC-001") if got == nil { t.Error("unarchived item should be returned by GetByPartNumber") } } func TestItemCreateRevision(t *testing.T) { database := mustConnectTestDB(t) repo := NewItemRepository(database) ctx := context.Background() item := &Item{PartNumber: "REV-001", ItemType: "part", Description: "revisable"} if err := repo.Create(ctx, item, map[string]any{"v": 1}); err != nil { t.Fatalf("Create: %v", err) } // Create second revision rev := &Revision{ ItemID: item.ID, Properties: map[string]any{"v": 2}, } if err := repo.CreateRevision(ctx, rev); err != nil { t.Fatalf("CreateRevision: %v", err) } if rev.RevisionNumber != 2 { t.Errorf("revision number: got %d, want 2", rev.RevisionNumber) } // Item's current_revision should be updated by trigger got, _ := repo.GetByPartNumber(ctx, "REV-001") if got.CurrentRevision != 2 { t.Errorf("current_revision: got %d, want 2", got.CurrentRevision) } } func TestItemGetRevisions(t *testing.T) { database := mustConnectTestDB(t) repo := NewItemRepository(database) ctx := context.Background() item := &Item{PartNumber: "REVS-001", ItemType: "part", Description: "multi rev"} if err := repo.Create(ctx, item, map[string]any{"step": "initial"}); err != nil { t.Fatalf("Create: %v", err) } comment := "second revision" repo.CreateRevision(ctx, &Revision{ ItemID: item.ID, Properties: map[string]any{"step": "updated"}, Comment: &comment, }) revisions, err := repo.GetRevisions(ctx, item.ID) if err != nil { t.Fatalf("GetRevisions: %v", err) } if len(revisions) != 2 { t.Errorf("expected 2 revisions, got %d", len(revisions)) } // Revisions are returned newest first if revisions[0].RevisionNumber != 2 { t.Errorf("first revision should be #2 (newest), got #%d", revisions[0].RevisionNumber) } } func TestItemSetThumbnailKey(t *testing.T) { database := mustConnectTestDB(t) repo := NewItemRepository(database) ctx := context.Background() item := &Item{PartNumber: "THUMB-001", ItemType: "part", Description: "thumbnail test"} if err := repo.Create(ctx, item, nil); err != nil { t.Fatalf("Create: %v", err) } if err := repo.SetThumbnailKey(ctx, item.ID, "items/thumb.png"); err != nil { t.Fatalf("SetThumbnailKey: %v", err) } got, _ := repo.GetByID(ctx, item.ID) if got.ThumbnailKey == nil || *got.ThumbnailKey != "items/thumb.png" { t.Errorf("thumbnail_key: got %v, want %q", got.ThumbnailKey, "items/thumb.png") } }