213 lines
6.9 KiB
Go
213 lines
6.9 KiB
Go
package batch
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"sub2api-cn-relay-manager/internal/host/sub2api"
|
|
"sub2api-cn-relay-manager/internal/store/sqlite"
|
|
)
|
|
|
|
func TestValidationService(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("confirmed or advisory with chat 200 becomes active", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, confirmationStatus := range []string{string(ConfirmationConfirmed), string(ConfirmationAdvisory)} {
|
|
confirmationStatus := confirmationStatus
|
|
t.Run(confirmationStatus, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
itemStore := &fakeValidationItemStore{}
|
|
runStore := &fakeValidationRunStore{
|
|
run: sqlite.ImportRun{RunID: "run-1", TotalItems: 1},
|
|
}
|
|
service := ValidationService{
|
|
ItemStore: itemStore,
|
|
RunStore: runStore,
|
|
Validator: func(ctx context.Context, item sqlite.ImportRunItem) (sub2api.GatewayCompletionResult, error) {
|
|
return sub2api.GatewayCompletionResult{OK: true, StatusCode: 200, ContentType: "application/json"}, nil
|
|
},
|
|
}
|
|
|
|
item := sqlite.ImportRunItem{
|
|
ItemID: "item-1",
|
|
RunID: "run-1",
|
|
CurrentStage: string(ItemStageValidate),
|
|
ConfirmationStatus: confirmationStatus,
|
|
AccessStatus: string(AccessStatusUnknown),
|
|
ResolvedSmokeModel: "kimi-k2.6",
|
|
APIKeyFingerprint: "sha256:abc",
|
|
MatchedAccountState: string(MatchedAccountStateActive),
|
|
AccountResolution: string(AccountResolutionReused),
|
|
}
|
|
|
|
if err := service.ValidateItem(context.Background(), item); err != nil {
|
|
t.Fatalf("ValidateItem() error = %v", err)
|
|
}
|
|
got := itemStore.last
|
|
if got.AccessStatus != string(AccessStatusActive) {
|
|
t.Fatalf("AccessStatus = %q, want active", got.AccessStatus)
|
|
}
|
|
if got.CurrentStage != string(ItemStageDone) {
|
|
t.Fatalf("CurrentStage = %q, want done", got.CurrentStage)
|
|
}
|
|
if runStore.updated.ActiveItems != 1 || runStore.updated.CompletedItems != 1 {
|
|
t.Fatalf("run summary = %+v, want active/completed increment", runStore.updated)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("exhausted transient completion becomes degraded", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
itemStore := &fakeValidationItemStore{}
|
|
runStore := &fakeValidationRunStore{
|
|
run: sqlite.ImportRun{RunID: "run-1", TotalItems: 1},
|
|
}
|
|
service := ValidationService{
|
|
ItemStore: itemStore,
|
|
RunStore: runStore,
|
|
Validator: func(ctx context.Context, item sqlite.ImportRunItem) (sub2api.GatewayCompletionResult, error) {
|
|
return sub2api.GatewayCompletionResult{
|
|
OK: false,
|
|
StatusCode: 503,
|
|
BodyPreview: `{"error":{"message":"no available accounts"}}`,
|
|
}, nil
|
|
},
|
|
}
|
|
|
|
item := sqlite.ImportRunItem{
|
|
ItemID: "item-1",
|
|
RunID: "run-1",
|
|
CurrentStage: string(ItemStageValidate),
|
|
ConfirmationStatus: string(ConfirmationConfirmed),
|
|
AccessStatus: string(AccessStatusUnknown),
|
|
ResolvedSmokeModel: "kimi-k2.6",
|
|
APIKeyFingerprint: "sha256:abc",
|
|
MatchedAccountState: string(MatchedAccountStateActive),
|
|
AccountResolution: string(AccountResolutionReused),
|
|
}
|
|
|
|
if err := service.ValidateItem(context.Background(), item); err != nil {
|
|
t.Fatalf("ValidateItem() error = %v", err)
|
|
}
|
|
if itemStore.last.AccessStatus != string(AccessStatusDegraded) {
|
|
t.Fatalf("AccessStatus = %q, want degraded", itemStore.last.AccessStatus)
|
|
}
|
|
if !strings.Contains(itemStore.last.AdvisoryMessagesJSON, "gateway_warmup_retry_succeeded") {
|
|
t.Fatalf("AdvisoryMessagesJSON = %q, want warmup advisory", itemStore.last.AdvisoryMessagesJSON)
|
|
}
|
|
if runStore.updated.DegradedItems != 1 || runStore.updated.WarningItems != 1 {
|
|
t.Fatalf("run summary = %+v, want degraded/warning increment", runStore.updated)
|
|
}
|
|
})
|
|
|
|
t.Run("definitive invalid path becomes broken", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
itemStore := &fakeValidationItemStore{}
|
|
runStore := &fakeValidationRunStore{
|
|
run: sqlite.ImportRun{RunID: "run-1", TotalItems: 1},
|
|
}
|
|
service := ValidationService{
|
|
ItemStore: itemStore,
|
|
RunStore: runStore,
|
|
Validator: func(ctx context.Context, item sqlite.ImportRunItem) (sub2api.GatewayCompletionResult, error) {
|
|
return sub2api.GatewayCompletionResult{
|
|
OK: false,
|
|
StatusCode: 404,
|
|
BodyPreview: `{"error":"route missing"}`,
|
|
}, nil
|
|
},
|
|
}
|
|
|
|
item := sqlite.ImportRunItem{
|
|
ItemID: "item-1",
|
|
RunID: "run-1",
|
|
CurrentStage: string(ItemStageValidate),
|
|
ConfirmationStatus: string(ConfirmationConfirmed),
|
|
AccessStatus: string(AccessStatusUnknown),
|
|
ResolvedSmokeModel: "kimi-k2.6",
|
|
APIKeyFingerprint: "sha256:abc",
|
|
MatchedAccountState: string(MatchedAccountStateActive),
|
|
AccountResolution: string(AccountResolutionReused),
|
|
}
|
|
|
|
if err := service.ValidateItem(context.Background(), item); err != nil {
|
|
t.Fatalf("ValidateItem() error = %v", err)
|
|
}
|
|
if itemStore.last.AccessStatus != string(AccessStatusBroken) {
|
|
t.Fatalf("AccessStatus = %q, want broken", itemStore.last.AccessStatus)
|
|
}
|
|
if runStore.updated.BrokenItems != 1 || runStore.updated.CompletedItems != 1 {
|
|
t.Fatalf("run summary = %+v, want broken/completed increment", runStore.updated)
|
|
}
|
|
})
|
|
|
|
t.Run("only validation stage may write access status", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
itemStore := &fakeValidationItemStore{}
|
|
runStore := &fakeValidationRunStore{
|
|
run: sqlite.ImportRun{RunID: "run-1", TotalItems: 1},
|
|
}
|
|
service := ValidationService{
|
|
ItemStore: itemStore,
|
|
RunStore: runStore,
|
|
Validator: func(ctx context.Context, item sqlite.ImportRunItem) (sub2api.GatewayCompletionResult, error) {
|
|
return sub2api.GatewayCompletionResult{OK: true, StatusCode: 200}, nil
|
|
},
|
|
}
|
|
|
|
item := sqlite.ImportRunItem{
|
|
ItemID: "item-1",
|
|
RunID: "run-1",
|
|
CurrentStage: string(ItemStageConfirm),
|
|
ConfirmationStatus: string(ConfirmationConfirmed),
|
|
AccessStatus: string(AccessStatusUnknown),
|
|
ResolvedSmokeModel: "kimi-k2.6",
|
|
APIKeyFingerprint: "sha256:abc",
|
|
MatchedAccountState: string(MatchedAccountStateActive),
|
|
AccountResolution: string(AccountResolutionReused),
|
|
}
|
|
|
|
err := service.ValidateItem(context.Background(), item)
|
|
if err == nil {
|
|
t.Fatal("ValidateItem() error = nil, want validation stage guard")
|
|
}
|
|
if itemStore.calls != 0 {
|
|
t.Fatalf("item upsert calls = %d, want 0", itemStore.calls)
|
|
}
|
|
})
|
|
}
|
|
|
|
type fakeValidationItemStore struct {
|
|
last sqlite.ImportRunItem
|
|
calls int
|
|
}
|
|
|
|
func (f *fakeValidationItemStore) Upsert(ctx context.Context, item sqlite.ImportRunItem) error {
|
|
f.last = item
|
|
f.calls++
|
|
return nil
|
|
}
|
|
|
|
type fakeValidationRunStore struct {
|
|
run sqlite.ImportRun
|
|
updated sqlite.ImportRun
|
|
}
|
|
|
|
func (f *fakeValidationRunStore) GetByRunID(ctx context.Context, runID string) (sqlite.ImportRun, error) {
|
|
return f.run, nil
|
|
}
|
|
|
|
func (f *fakeValidationRunStore) Update(ctx context.Context, run sqlite.ImportRun) error {
|
|
f.updated = run
|
|
f.run = run
|
|
return nil
|
|
}
|