package usecase import ( "context" "errors" "testing" "text/template" "gitea.maximumdirect.net/eric/promptkit/internal/domain" "gitea.maximumdirect.net/eric/promptkit/internal/prompt" "gitea.maximumdirect.net/eric/promptkit/internal/promptdef" ) type requirementInspectionPromptRepository struct { definition *domain.PromptDefinition snapshot prompt.RequirementSnapshot lookupErr error snapshotFound bool lookupCalls int snapshotCalls int lookupID string snapshotID string } type requirementPromptMetadataRepository struct { *requirementInspectionPromptRepository inspection domain.PromptInspection lookupID string } type inspectionProfileIdentityRepository struct { *inspectionProfileRepository identity string identityErr error identityID string identityCalls int } func (repository *inspectionProfileIdentityRepository) GetProfileIdentity( _ context.Context, id string, ) (string, error) { repository.identityCalls++ repository.identityID = id return repository.identity, repository.identityErr } func (repository *requirementInspectionPromptRepository) GetPromptDefinition( context.Context, string, ) (*domain.PromptDefinition, error) { return repository.definition, repository.lookupErr } func (repository *requirementPromptMetadataRepository) GetPromptInspection( _ context.Context, id string, ) (*domain.PromptInspection, error) { repository.lookupID = id inspection := repository.inspection inspection.Inputs = append([]domain.PromptInput(nil), inspection.Inputs...) return &inspection, nil } func (repository *requirementInspectionPromptRepository) GetPromptSnapshot( _ context.Context, id string, ) (*domain.PromptDefinition, string, error) { repository.lookupCalls++ repository.lookupID = id return repository.definition, "captured-hash", repository.lookupErr } func (repository *requirementInspectionPromptRepository) GetRequirementSnapshot( id string, ) (prompt.RequirementSnapshot, bool) { repository.snapshotCalls++ repository.snapshotID = id return repository.snapshot, repository.snapshotFound } func TestRunnerInspectRequirementsUsesCapturedSelectionWithoutExecutionWork(t *testing.T) { message, err := prompt.CompileTemplate("message", `{{input "message-input"}} {{.caller}}`) if err != nil { t.Fatal(err) } session, err := prompt.CompileTemplate("session", `{{input "session-only"}}`) if err != nil { t.Fatal(err) } definition := &domain.PromptDefinition{ID: "selected", Version: stringPtr("v2"), DefaultProfile: "default-profile"} repository := &requirementInspectionPromptRepository{ definition: definition, snapshotFound: true, snapshot: prompt.RequirementSnapshot{Templates: prompt.CompiledTemplates{ Messages: []*template.Template{message}, Session: session, }}, } profiles := &inspectionProfileRepository{profile: &domain.ExecutionProfile{ ID: "partial-profile", ResolvedTarget: &domain.ExecutionTarget{Endpoint: ""}, }} runner := &Runner{promptDefs: repository, profiles: profiles} inspection, err := runner.InspectRequirements(context.Background(), RequirementInspectionRequest{ PromptID: " requested ", ProfileID: "partial-profile", Vars: map[string]string{"caller": ""}, SessionID: " direct-session ", }) if err != nil { t.Fatalf("inspect requirements: %v", err) } if repository.lookupCalls != 1 || repository.lookupID != " requested " || repository.snapshotCalls != 1 || repository.snapshotID != definition.ID { t.Fatalf("selection lookup=(%d,%q), snapshot=(%d,%q)", repository.lookupCalls, repository.lookupID, repository.snapshotCalls, repository.snapshotID) } if profiles.calls != 1 || profiles.id != "partial-profile" || inspection.PromptID != definition.ID || inspection.PromptVersion == nil || *inspection.PromptVersion != *definition.Version || inspection.ProfileID != "partial-profile" || !inspection.Complete || len(inspection.Inputs) != 1 || inspection.Inputs[0].Name != "message-input" || len(inspection.Variables) != 1 || inspection.Variables[0].Name != "caller" || !inspection.Variables[0].Supplied { t.Fatalf("unexpected requirement inspection: %+v", inspection) } } func TestRunnerInspectRequirementsUsesPromptMetadataAndExactRequirementSnapshot(t *testing.T) { message, err := prompt.CompileTemplate("message", `{{input "message-input"}} {{.caller}}`) if err != nil { t.Fatal(err) } session, err := prompt.CompileTemplate("session", `{{input "session-only"}}`) if err != nil { t.Fatal(err) } baseRepository := &requirementInspectionPromptRepository{ snapshotFound: true, snapshot: prompt.RequirementSnapshot{Templates: prompt.CompiledTemplates{ Messages: []*template.Template{message}, Session: session, }}, } promptRepository := &requirementPromptMetadataRepository{ requirementInspectionPromptRepository: baseRepository, inspection: domain.PromptInspection{ PromptID: "captured-id", PromptVersion: stringPtr("captured-version"), PromptHash: "captured-hash", DefaultProfileID: "default-profile", }, } profiles := &inspectionProfileIdentityRepository{ inspectionProfileRepository: &inspectionProfileRepository{}, identity: "default-profile", } inspection, err := (&Runner{promptDefs: promptRepository, profiles: profiles}).InspectRequirements( context.Background(), RequirementInspectionRequest{ PromptID: " requested-id ", Vars: map[string]string{"caller": "Ada"}, SessionID: " direct-session ", }, ) if err != nil || promptRepository.lookupID != " requested-id " || baseRepository.snapshotID != "captured-id" || profiles.identityID != "default-profile" || inspection.PromptID != "captured-id" || inspection.PromptVersion == nil || *inspection.PromptVersion != "captured-version" || inspection.ProfileID != "default-profile" || !inspection.Complete || len(inspection.Inputs) != 1 || inspection.Inputs[0].Name != "message-input" || len(inspection.Variables) != 1 || inspection.Variables[0].Name != "caller" || !inspection.Variables[0].Supplied { t.Fatalf("metadata-based requirement inspection=(%+v, %v), prompt lookup=%q, snapshot=%q, profile=%q", inspection, err, promptRepository.lookupID, baseRepository.snapshotID, profiles.identityID) } } func TestRunnerInspectRequirementsUsesProfileIdentityCapabilityOrFallback(t *testing.T) { message, err := prompt.CompileTemplate("message", `{{.name}}`) if err != nil { t.Fatal(err) } definition := &domain.PromptDefinition{ID: "selected", Version: stringPtr("v1")} makePromptRepository := func() *requirementInspectionPromptRepository { return &requirementInspectionPromptRepository{ definition: definition, snapshotFound: true, snapshot: prompt.RequirementSnapshot{Templates: prompt.CompiledTemplates{Messages: []*template.Template{message}}}, } } requestedID := " literal-profile " t.Run("optional capability", func(t *testing.T) { profiles := &inspectionProfileIdentityRepository{ inspectionProfileRepository: &inspectionProfileRepository{profile: &domain.ExecutionProfile{ ID: "repository-profile", ResolvedTarget: &domain.ExecutionTarget{}, }}, identity: requestedID, } inspection, err := (&Runner{promptDefs: makePromptRepository(), profiles: profiles}).InspectRequirements( context.Background(), RequirementInspectionRequest{ PromptID: "selected", ProfileID: requestedID, Vars: map[string]string{"name": "Ada"}, }, ) if err != nil || inspection.ProfileID != requestedID || profiles.identityCalls != 1 || profiles.identityID != requestedID || profiles.calls != 0 { t.Fatalf("identity capability inspection=(%+v, %v), capability calls=%d id=%q full lookups=%d", inspection, err, profiles.identityCalls, profiles.identityID, profiles.calls) } }) t.Run("full-profile fallback", func(t *testing.T) { profiles := &inspectionProfileRepository{profile: &domain.ExecutionProfile{ ID: "repository-profile", ResolvedTarget: &domain.ExecutionTarget{}, }} inspection, err := (&Runner{promptDefs: makePromptRepository(), profiles: profiles}).InspectRequirements( context.Background(), RequirementInspectionRequest{ PromptID: "selected", ProfileID: requestedID, Vars: map[string]string{"name": "Ada"}, }, ) if err != nil || inspection.ProfileID != requestedID || profiles.calls != 1 || profiles.id != requestedID { t.Fatalf("fallback inspection=(%+v, %v), full lookup=(%d,%q)", inspection, err, profiles.calls, profiles.id) } }) t.Run("capability failure", func(t *testing.T) { lookupErr := errors.New("profile identity lookup failed") profiles := &inspectionProfileIdentityRepository{ inspectionProfileRepository: &inspectionProfileRepository{}, identityErr: lookupErr, } _, err := (&Runner{promptDefs: makePromptRepository(), profiles: profiles}).InspectRequirements( context.Background(), RequirementInspectionRequest{PromptID: "selected", ProfileID: requestedID}, ) if !errors.Is(err, ErrProfileLoad) || !errors.Is(err, lookupErr) || profiles.identityCalls != 1 || profiles.calls != 0 { t.Fatalf("capability failure=(%v), capability calls=%d full lookups=%d", err, profiles.identityCalls, profiles.calls) } }) } func TestRunnerInspectRequirementsUsesPromptDefaultAndIncludesSessionWhenNotBypassed(t *testing.T) { message, err := prompt.CompileTemplate("message", `{{input "message-input"}}`) if err != nil { t.Fatal(err) } session, err := prompt.CompileTemplate("session", `{{input "session-input"}}`) if err != nil { t.Fatal(err) } definition := &domain.PromptDefinition{ID: "selected", Version: stringPtr("v1"), DefaultProfile: "default-profile"} repository := &requirementInspectionPromptRepository{ definition: definition, snapshotFound: true, snapshot: prompt.RequirementSnapshot{Templates: prompt.CompiledTemplates{Messages: []*template.Template{message}, Session: session}}, } profiles := &inspectionProfileRepository{profile: &domain.ExecutionProfile{ ID: "default-profile", ResolvedTarget: &domain.ExecutionTarget{}, }} inspection, err := (&Runner{promptDefs: repository, profiles: profiles}).InspectRequirements( context.Background(), RequirementInspectionRequest{PromptID: "selected"}, ) if err != nil { t.Fatalf("inspect requirements: %v", err) } if profiles.id != "default-profile" || inspection.ProfileID != "default-profile" || len(inspection.Inputs) != 2 || inspection.Inputs[0].Name != "message-input" || inspection.Inputs[1].Name != "session-input" { t.Fatalf("prompt-default profile or session template was not selected: %+v", inspection) } } func TestRunnerInspectRequirementsReturnsIncompleteDiscoveryAsSuccess(t *testing.T) { compiled, err := prompt.CompileTemplate("message", `{{if .missing}}{{input "left"}}{{else}}{{input "right"}}{{end}}`) if err != nil { t.Fatal(err) } repository := &requirementInspectionPromptRepository{ definition: &domain.PromptDefinition{ID: "selected", Version: stringPtr("v1"), DefaultProfile: "profile"}, snapshotFound: true, snapshot: prompt.RequirementSnapshot{Templates: prompt.CompiledTemplates{Messages: []*template.Template{compiled}}}, } profiles := &inspectionProfileRepository{profile: &domain.ExecutionProfile{ID: "profile", ResolvedTarget: &domain.ExecutionTarget{}}} inspection, err := (&Runner{promptDefs: repository, profiles: profiles}).InspectRequirements( context.Background(), RequirementInspectionRequest{PromptID: "selected"}, ) if err != nil || inspection == nil || inspection.Complete || len(inspection.Issues) != 1 || inspection.Issues[0].Code != domain.RequirementIssueMissingVariable { t.Fatalf("incomplete discovery returned (%+v, %v), want successful partial result", inspection, err) } } func TestRunnerInspectRequirementsClassifiesSelectionAndSnapshotFailures(t *testing.T) { t.Run("blank prompt", func(t *testing.T) { repository := &requirementInspectionPromptRepository{} _, err := (&Runner{promptDefs: repository}).InspectRequirements(context.Background(), RequirementInspectionRequest{PromptID: " \t "}) if !errors.Is(err, ErrInvalidRequest) || repository.lookupCalls != 0 { t.Fatalf("blank prompt error=%v lookups=%d", err, repository.lookupCalls) } }) t.Run("invalid session", func(t *testing.T) { repository := &requirementInspectionPromptRepository{} _, err := (&Runner{promptDefs: repository}).InspectRequirements(context.Background(), RequirementInspectionRequest{ PromptID: "selected", SessionID: string([]byte{0xff}), }) if !errors.Is(err, ErrInvalidRequest) || repository.lookupCalls != 0 { t.Fatalf("invalid session error=%v lookups=%d", err, repository.lookupCalls) } }) t.Run("cancellation before lookup", func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() repository := &requirementInspectionPromptRepository{} _, err := (&Runner{promptDefs: repository}).InspectRequirements(ctx, RequirementInspectionRequest{PromptID: "selected"}) if !errors.Is(err, ErrPromptLoad) || !errors.Is(err, context.Canceled) || repository.lookupCalls != 0 { t.Fatalf("canceled inspection=%v lookups=%d", err, repository.lookupCalls) } }) t.Run("no profile selection", func(t *testing.T) { repository := &requirementInspectionPromptRepository{definition: &domain.PromptDefinition{ID: "selected"}} _, err := (&Runner{promptDefs: repository}).InspectRequirements(context.Background(), RequirementInspectionRequest{PromptID: "selected"}) if !errors.Is(err, ErrInvalidRequest) || !errors.Is(err, ErrProfileRequired) { t.Fatalf("missing profile error=%v", err) } }) t.Run("prompt lookup failure", func(t *testing.T) { lookupFailure := errors.New("prompt lookup failed") repository := &requirementInspectionPromptRepository{lookupErr: lookupFailure} _, err := (&Runner{promptDefs: repository}).InspectRequirements(context.Background(), RequirementInspectionRequest{PromptID: "selected"}) if !errors.Is(err, ErrPromptLoad) || !errors.Is(err, lookupFailure) { t.Fatalf("prompt lookup error=%v", err) } }) t.Run("captured snapshot unavailable", func(t *testing.T) { repository := &requirementInspectionPromptRepository{definition: &domain.PromptDefinition{ID: "selected", DefaultProfile: "profile"}} profiles := &inspectionProfileRepository{profile: &domain.ExecutionProfile{ID: "profile", ResolvedTarget: &domain.ExecutionTarget{}}} _, err := (&Runner{promptDefs: repository, profiles: profiles}).InspectRequirements(context.Background(), RequirementInspectionRequest{PromptID: "selected"}) if !errors.Is(err, ErrPromptLoad) || repository.snapshotCalls != 1 { t.Fatalf("missing snapshot error=%v calls=%d", err, repository.snapshotCalls) } }) t.Run("repository lacks snapshot capability", func(t *testing.T) { _, err := (&Runner{promptDefs: &inspectionPromptRepository{definition: &domain.PromptDefinition{ID: "selected", DefaultProfile: "profile"}}, profiles: &inspectionProfileRepository{profile: &domain.ExecutionProfile{ID: "profile", ResolvedTarget: &domain.ExecutionTarget{}}}, }).InspectRequirements(context.Background(), RequirementInspectionRequest{PromptID: "selected"}) if !errors.Is(err, ErrPromptLoad) { t.Fatalf("missing capability error=%v", err) } }) if _, err := (*Runner)(nil).InspectRequirements(context.Background(), RequirementInspectionRequest{PromptID: "selected"}); !errors.Is(err, ErrPromptLoad) { t.Fatalf("nil runner error=%v", err) } if _, err := (&Runner{}).InspectRequirements(context.Background(), RequirementInspectionRequest{PromptID: "selected"}); !errors.Is(err, ErrPromptLoad) { t.Fatalf("nil repository error=%v", err) } if _, err := (&Runner{promptDefs: &requirementInspectionPromptRepository{definition: &domain.PromptDefinition{ID: "selected"}}}).InspectRequirements( context.Background(), RequirementInspectionRequest{PromptID: "selected", ProfileID: "explicit"}, ); !errors.Is(err, ErrProfileLoad) { t.Fatalf("missing profile error=%v", err) } if _, err := (&Runner{promptDefs: &requirementInspectionPromptRepository{definition: &domain.PromptDefinition{ID: "selected", DefaultProfile: "profile"}}, profiles: &inspectionProfileRepository{profile: &domain.ExecutionProfile{ID: "profile"}}, }).InspectRequirements(context.Background(), RequirementInspectionRequest{PromptID: "selected"}); !errors.Is(err, ErrProfileLoad) { t.Fatalf("unresolved profile error=%v", err) } if _, err := (&Runner{promptDefs: &requirementInspectionPromptRepository{lookupErr: promptdef.ErrPromptDefinitionNotFound}}).InspectRequirements( context.Background(), RequirementInspectionRequest{PromptID: "missing"}, ); !errors.Is(err, ErrPromptLoad) || !errors.Is(err, promptdef.ErrPromptDefinitionNotFound) { t.Fatalf("missing prompt error=%v", err) } }