package drivers import ( "context" "errors" "testing" "time" "github.com/jackc/pgx/v5/pgconn" ) type memoryStore struct { drivers map[string]Driver } func newMemoryStore() *memoryStore { return &memoryStore{drivers: make(map[string]Driver)} } func (m *memoryStore) Exec(ctx context.Context, sql string) (int64, error) { return 0, nil } func (m *memoryStore) Create(ctx context.Context, d Driver) error { for _, existing := range m.drivers { if existing.Nickname == d.Nickname { return &pgconn.PgError{Code: "23505", Message: "duplicate key value violates unique constraint"} } } m.drivers[d.ID] = d return nil } func (m *memoryStore) Get(ctx context.Context, id string) (Driver, error) { d, ok := m.drivers[id] if !ok { return Driver{}, ErrNotFound } return d, nil } func (m *memoryStore) GetByNickname(ctx context.Context, nick string) (Driver, error) { for _, d := range m.drivers { if d.Nickname == nick { return d, nil } } return Driver{}, ErrNotFound } func (m *memoryStore) List(ctx context.Context, limit, offset int, clanID string) ([]Driver, error) { out := make([]Driver, 0) for _, d := range m.drivers { if clanID == "" || d.ClanID == clanID { out = append(out, d) } } return out, nil } func (m *memoryStore) Delete(ctx context.Context, id string) error { if _, ok := m.drivers[id]; !ok { return ErrNotFound } delete(m.drivers, id) return nil } func (m *memoryStore) Update(ctx context.Context, d Driver) error { if _, ok := m.drivers[d.ID]; !ok { return ErrNotFound } for id, existing := range m.drivers { if id != d.ID && existing.Nickname == d.Nickname { return &pgconn.PgError{Code: "23505", Message: "duplicate key value violates unique constraint"} } } m.drivers[d.ID] = d return nil } func (m *memoryStore) Count(ctx context.Context) (int, error) { return len(m.drivers), nil } func TestDriversService(t *testing.T) { store := newMemoryStore() svc := NewService(store) svc.now = func() time.Time { return time.Unix(1700000000, 0) } ctx := context.Background() // 1. Create a driver d1, err := svc.Create(ctx, CreateInput{ ID: "driver-1", Nickname: "HAM", Name: "Lewis Hamilton", AvatarURL: "https://example.com/ham.png", ClanID: "clan-1", }) if err != nil { t.Fatalf("Create failed: %v", err) } if d1.ID != "driver-1" || d1.Nickname != "HAM" || d1.Name != "Lewis Hamilton" || d1.ClanID != "clan-1" { t.Errorf("Unexpected created driver: %+v", d1) } // 2. Validate Nickname must be 3 uppercase ASCII letters _, err = svc.Create(ctx, CreateInput{Nickname: "HAMILTON", Name: "toolong"}) if !errors.Is(err, ErrInvalidInput) { t.Errorf("Expected ErrInvalidInput for tag 'HAMILTON', got: %v", err) } // 3. Validate empty Name is rejected _, err = svc.Create(ctx, CreateInput{Nickname: "BOT", Name: ""}) if !errors.Is(err, ErrInvalidInput) { t.Errorf("Expected ErrInvalidInput for empty name, got: %v", err) } // 4. Duplicate nickname check _, err = svc.Create(ctx, CreateInput{ ID: "driver-2", Nickname: "HAM", Name: "Another Lewis", }) if !errors.Is(err, ErrAlreadyExists) { t.Errorf("Expected ErrAlreadyExists for duplicate nickname, got: %v", err) } // 5. Get driver got, err := svc.Get(ctx, "driver-1") if err != nil { t.Fatalf("Get failed: %v", err) } if got.Nickname != "HAM" { t.Errorf("Expected HAM, got %s", got.Nickname) } // 6. Get by nickname gotByNick, err := svc.GetByNickname(ctx, "ham") // should normalize to HAM if err != nil { t.Fatalf("GetByNickname failed: %v", err) } if gotByNick.ID != "driver-1" { t.Errorf("Expected driver-1, got %s", gotByNick.ID) } // 7. Update driver (including nickname) clanID := "clan-2" updated, err := svc.Update(ctx, "driver-1", UpdateInput{ Nickname: "VET", Name: "Lewis Hamilton MBE", AvatarURL: "https://example.com/ham-mbe.png", ClanID: &clanID, }) if err != nil { t.Fatalf("Update failed: %v", err) } if updated.Nickname != "VET" || updated.Name != "Lewis Hamilton MBE" || updated.AvatarURL != "https://example.com/ham-mbe.png" || updated.ClanID != "clan-2" { t.Errorf("Unexpected updated driver fields: %+v", updated) } // 7a. Update with invalid nickname _, err = svc.Update(ctx, "driver-1", UpdateInput{Nickname: "TOOLONG"}) if !errors.Is(err, ErrInvalidInput) { t.Errorf("Expected ErrInvalidInput for invalid nickname update, got: %v", err) } // 7b. Create driver-2 to test duplicate nickname conflict _, err = svc.Create(ctx, CreateInput{ ID: "driver-2", Nickname: "HAM", Name: "Lewis", }) if err != nil { t.Fatalf("Create driver-2 failed: %v", err) } // 7c. Update driver-2 to nickname "VET" (should fail, already taken by driver-1) _, err = svc.Update(ctx, "driver-2", UpdateInput{Nickname: "VET"}) if !errors.Is(err, ErrAlreadyExists) { t.Errorf("Expected ErrAlreadyExists for duplicate nickname update, got: %v", err) } // 8. Delete driver err = svc.Delete(ctx, "driver-1") if err != nil { t.Fatalf("Delete failed: %v", err) } _, err = svc.Get(ctx, "driver-1") if !errors.Is(err, ErrNotFound) { t.Errorf("Expected ErrNotFound after deletion, got: %v", err) } }