214 lines
6.9 KiB
Go
214 lines
6.9 KiB
Go
package usecase
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"gitea.maximumdirect.net/eric/promptkit/internal/defaults"
|
|
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
|
|
"gitea.maximumdirect.net/eric/promptkit/internal/profile"
|
|
)
|
|
|
|
type inspectionProfileRepository struct {
|
|
profile *domain.ExecutionProfile
|
|
err error
|
|
calls int
|
|
id string
|
|
}
|
|
|
|
func (r *inspectionProfileRepository) GetProfile(
|
|
_ context.Context,
|
|
id string,
|
|
) (*domain.ExecutionProfile, error) {
|
|
r.calls++
|
|
r.id = id
|
|
if r.err != nil {
|
|
return nil, r.err
|
|
}
|
|
return r.profile, nil
|
|
}
|
|
|
|
type inspectionBackendResolver struct {
|
|
backend domain.Backend
|
|
err error
|
|
calls int
|
|
id string
|
|
}
|
|
|
|
func (r *inspectionBackendResolver) GetBackend(id string) (domain.Backend, error) {
|
|
r.calls++
|
|
r.id = id
|
|
if r.err != nil {
|
|
return domain.Backend{}, r.err
|
|
}
|
|
return r.backend, nil
|
|
}
|
|
|
|
func TestRunnerInspectProfileResolvesProfileAndBackendOnce(t *testing.T) {
|
|
profiles := &inspectionProfileRepository{profile: &domain.ExecutionProfile{
|
|
ID: "profile",
|
|
BackendID: " backend ",
|
|
Model: "profile-model",
|
|
Temperature: 0.4,
|
|
MaxTokens: 32,
|
|
TimeoutSeconds: 45,
|
|
ServiceTier: "priority",
|
|
ReasoningEffort: "high",
|
|
ExtraParams: map[string]any{
|
|
"profile": "value",
|
|
},
|
|
}}
|
|
backends := &inspectionBackendResolver{backend: domain.Backend{
|
|
ID: "backend",
|
|
Endpoint: "https://backend.example/v1",
|
|
APIKeyEnv: "BACKEND_KEY",
|
|
ExtraParams: map[string]any{
|
|
"backend": "value",
|
|
},
|
|
}}
|
|
runner := &Runner{profiles: profiles, backends: backends}
|
|
|
|
inspection, err := runner.InspectProfile(context.Background(), " profile ")
|
|
if err != nil {
|
|
t.Fatalf("inspect profile: %v", err)
|
|
}
|
|
if profiles.calls != 1 || profiles.id != "profile" {
|
|
t.Fatalf("profile lookup=(calls=%d id=%q), want one exact lookup", profiles.calls, profiles.id)
|
|
}
|
|
if backends.calls != 1 || backends.id != "backend" {
|
|
t.Fatalf("backend lookup=(calls=%d id=%q), want one exact lookup", backends.calls, backends.id)
|
|
}
|
|
if profiles.profile.BackendID != " backend " {
|
|
t.Fatalf("inspection mutated repository profile backend: %q", profiles.profile.BackendID)
|
|
}
|
|
|
|
wantTarget := domain.ExecutionTarget{
|
|
BackendID: "backend",
|
|
Endpoint: "https://backend.example/v1",
|
|
Model: "profile-model",
|
|
Temperature: 0.4,
|
|
MaxTokens: 32,
|
|
TopP: defaults.ExecutionTargetDefault().TopP,
|
|
TimeoutSeconds: 45,
|
|
ServiceTier: "priority",
|
|
ReasoningEffort: "high",
|
|
APIKeyEnv: "BACKEND_KEY",
|
|
ExtraParams: map[string]any{
|
|
"profile": "value",
|
|
},
|
|
}
|
|
if inspection.ProfileID != "profile" || inspection.APIKeyRequired ||
|
|
!reflect.DeepEqual(inspection.EffectiveModelParams, wantTarget) {
|
|
t.Fatalf("inspection=%#v, want profile=%q target=%#v", inspection, "profile", wantTarget)
|
|
}
|
|
}
|
|
|
|
func TestRunnerInspectProfileDoesNotNeedExecutionCollaboratorsOrCredentials(t *testing.T) {
|
|
t.Setenv("PROMPTKIT_INSPECTION_TEST_KEY", "")
|
|
profiles := &inspectionProfileRepository{profile: &domain.ExecutionProfile{
|
|
ID: "endpoint-only",
|
|
Endpoint: "https://profile.example/v1",
|
|
Model: "profile-model",
|
|
APIKeyEnv: "PROMPTKIT_INSPECTION_TEST_KEY",
|
|
}}
|
|
runner := &Runner{profiles: profiles}
|
|
|
|
inspection, err := runner.InspectProfile(context.Background(), "endpoint-only")
|
|
if err != nil {
|
|
t.Fatalf("inspect endpoint-only profile: %v", err)
|
|
}
|
|
if inspection.EffectiveModelParams.BackendID != "" ||
|
|
inspection.EffectiveModelParams.APIKeyEnv != "PROMPTKIT_INSPECTION_TEST_KEY" ||
|
|
inspection.APIKeyRequired {
|
|
t.Fatalf("unexpected endpoint-only inspection: %#v", inspection)
|
|
}
|
|
}
|
|
|
|
func TestRunnerInspectProfileDirectCredentialRequirementClearsBackendEnvironment(t *testing.T) {
|
|
profiles := &inspectionProfileRepository{profile: &domain.ExecutionProfile{
|
|
ID: "direct-key",
|
|
BackendID: "backend",
|
|
Model: "profile-model",
|
|
APIKeyRequired: true,
|
|
}}
|
|
backends := &inspectionBackendResolver{backend: domain.Backend{
|
|
ID: "backend",
|
|
Endpoint: "https://backend.example/v1",
|
|
APIKeyEnv: "BACKEND_KEY",
|
|
}}
|
|
|
|
inspection, err := (&Runner{profiles: profiles, backends: backends}).InspectProfile(
|
|
context.Background(),
|
|
"direct-key",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("inspect direct-key profile: %v", err)
|
|
}
|
|
if !inspection.APIKeyRequired || inspection.EffectiveModelParams.APIKeyEnv != "" {
|
|
t.Fatalf("credential requirement was not resolved exclusively: %#v", inspection)
|
|
}
|
|
}
|
|
|
|
func TestRunnerInspectProfileClassifiesFailuresWithoutRepositoryWorkAfterCancellation(t *testing.T) {
|
|
t.Run("blank ID", func(t *testing.T) {
|
|
profiles := &inspectionProfileRepository{}
|
|
_, err := (&Runner{profiles: profiles}).InspectProfile(context.Background(), " \t ")
|
|
if !errors.Is(err, ErrInvalidRequest) || profiles.calls != 0 {
|
|
t.Fatalf("blank inspection=(%v, calls=%d), want invalid request without lookup", err, profiles.calls)
|
|
}
|
|
})
|
|
|
|
t.Run("canceled context", func(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
profiles := &inspectionProfileRepository{}
|
|
_, err := (&Runner{profiles: profiles}).InspectProfile(ctx, "profile")
|
|
if !errors.Is(err, ErrProfileLoad) || !errors.Is(err, context.Canceled) || profiles.calls != 0 {
|
|
t.Fatalf("canceled inspection=(%v, calls=%d), want profile load and context identities without lookup", err, profiles.calls)
|
|
}
|
|
})
|
|
|
|
t.Run("missing profile", func(t *testing.T) {
|
|
profiles := &inspectionProfileRepository{err: profile.ErrProfileNotFound}
|
|
_, err := (&Runner{profiles: profiles}).InspectProfile(context.Background(), "missing")
|
|
if !errors.Is(err, ErrProfileLoad) || !errors.Is(err, profile.ErrProfileNotFound) {
|
|
t.Fatalf("missing profile error=%v, want profile load and not-found identities", err)
|
|
}
|
|
})
|
|
|
|
t.Run("unknown backend", func(t *testing.T) {
|
|
backendErr := errors.New("unknown backend")
|
|
profiles := &inspectionProfileRepository{profile: &domain.ExecutionProfile{
|
|
ID: "profile", BackendID: "backend", Model: "profile-model",
|
|
}}
|
|
backends := &inspectionBackendResolver{err: backendErr}
|
|
_, err := (&Runner{profiles: profiles, backends: backends}).InspectProfile(context.Background(), "profile")
|
|
if !errors.Is(err, ErrProfileLoad) || !errors.Is(err, backendErr) {
|
|
t.Fatalf("unknown backend error=%v, want profile load and backend identities", err)
|
|
}
|
|
})
|
|
|
|
t.Run("defensive invalid dependencies", func(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
runner *Runner
|
|
}{
|
|
{name: "nil repository", runner: &Runner{}},
|
|
{name: "nil profile", runner: &Runner{profiles: &inspectionProfileRepository{}}},
|
|
{name: "invalid target", runner: &Runner{profiles: &inspectionProfileRepository{
|
|
profile: &domain.ExecutionProfile{ID: "profile", Endpoint: "https://profile.example/v1"},
|
|
}}},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := tc.runner.InspectProfile(context.Background(), "profile")
|
|
if !errors.Is(err, ErrProfileLoad) {
|
|
t.Fatalf("inspection error=%v, want profile load", err)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|