package actor import ( "fmt" "strings" "testing" "time" v1 "git.tipsy.codes/charles/webstory/pkg/api/webstory/v1" "google.golang.org/protobuf/types/known/timestamppb" ) func TestActorTranslator_FromAPI(t *testing.T) { now := time.Now() tests := []struct { name string apiActor *v1.Actor expectErr bool errMsg string checkFunc func(*DBActor) error }{ { name: "nil actor returns error", apiActor: nil, expectErr: true, errMsg: "api actor cannot be nil", }, { name: "minimal valid actor creates translator correctly", apiActor: &v1.Actor{ ActorId: "actor1", NameValue: "Player", }, expectErr: false, checkFunc: func(a *DBActor) error { if a.ActorID != "actor1" { return fmt.Errorf("expected ActorID actor1, got %s", a.ActorID) } if a.NameValue != "Player" { return fmt.Errorf("expected NameValue Player, got %s", a.NameValue) } return nil }, }, { name: "actor with all fields converts correctly", apiActor: &v1.Actor{ Name: "stories/my-actor-1", ActorId: "my-actor-1", NameValue: "The Hero", Role: "protagonist", Notes: "Main character", CreateTime: timestamppb.New(now), UpdateTime: timestamppb.New(now), Etag: "etag-123", }, expectErr: false, checkFunc: func(a *DBActor) error { if a.ActorID != "my-actor-1" { return fmt.Errorf("expected ActorID my-actor-1, got %s", a.ActorID) } if a.NameValue != "The Hero" { return fmt.Errorf("expected NameValue The Hero, got %s", a.NameValue) } if a.Role != "protagonist" { return fmt.Errorf("expected Role protagonist, got %s", a.Role) } if a.Notes != "Main character" { return fmt.Errorf("expected Notes Main character, got %s", a.Notes) } if a.Etag != "etag-123" { return fmt.Errorf("expected Etag etag-123, got %s", a.Etag) } return nil }, }, { name: "empty notes converted to empty string", apiActor: &v1.Actor{ ActorId: "actor2", NameValue: "NPC", Notes: "", }, expectErr: false, checkFunc: func(a *DBActor) error { if a.Notes != "" { return fmt.Errorf("expected empty Notes, got %s", a.Notes) } return nil }, }, { name: "missing name_value returns error", apiActor: &v1.Actor{ ActorId: "actor4", }, expectErr: true, errMsg: "name_value is required", }, { name: "invalid actor_id_short returns error", apiActor: &v1.Actor{ NameValue: "Test", }, expectErr: true, errMsg: "invalid actor_id format: must be 4-63 characters, matching pattern /[a-z][0-9-][0-9]$/", }, { name: "invalid actor_id_uppercase returns error", apiActor: &v1.Actor{ ActorId: "Actor2", }, expectErr: true, errMsg: "invalid actor_id format: must be 4-63 characters, matching pattern /[a-z][0-9-][0-9]$/", }, { name: "invalid actor_id_starts_with_number returns error", apiActor: &v1.Actor{ ActorId: "1actor", }, expectErr: true, errMsg: "invalid actor_id format: must be 4-63 characters, matching pattern /[a-z][0-9-][0-9]$/", }, { name: "valid actor_id_with_hyphens_and_numbers", apiActor: &v1.Actor{ NameValue: "Test Actor", ActorId: "my-awesome-actor-123", }, expectErr: false, checkFunc: func(a *DBActor) error { if a.ActorID != "my-awesome-actor-123" { return fmt.Errorf("expected ActorID my-awesome-actor-123, got %s", a.ActorID) } return nil }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { translator := NewActorTranslator() got, err := translator.FromAPI(tt.apiActor) if tt.expectErr { if err == nil { t.Fatalf("expected error but got nil") } if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) { t.Errorf("expected error message to contain %q, got %q", tt.errMsg, err.Error()) } return } if err != nil { t.Fatalf("unexpected error: %v", err) } if tt.checkFunc != nil { if err := tt.checkFunc(got); err != nil { t.Errorf("checkFunc failed: %v", err) } } }) } }