150 lines
4.9 KiB
Go
150 lines
4.9 KiB
Go
package probe
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"supply-intelligence/internal/domain"
|
|
"supply-intelligence/internal/repository"
|
|
)
|
|
|
|
func TestServiceEvaluateHTTPResultSuccess(t *testing.T) {
|
|
repo := repository.NewMemoryRepository()
|
|
service := NewService(repo)
|
|
service.now = func() time.Time { return time.Unix(1000, 0).UTC() }
|
|
|
|
result, err := service.EvaluateHTTPResult(context.Background(), EvaluateInput{
|
|
AccountID: 1,
|
|
Platform: "openai",
|
|
CurrentStatus: domain.AccountStatusSuspended,
|
|
StatusCode: 200,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if result.Classification != domain.ProbeClassificationSuccess {
|
|
t.Fatalf("unexpected classification: %q", result.Classification)
|
|
}
|
|
if result.RoutingState.AccountStatus != domain.AccountStatusActive {
|
|
t.Fatalf("unexpected account status: %q", result.RoutingState.AccountStatus)
|
|
}
|
|
if !result.RoutingState.RoutingEnabled {
|
|
t.Fatalf("expected routing enabled")
|
|
}
|
|
if result.RoutingState.ReasonCode != "ok" {
|
|
t.Fatalf("unexpected reason code: %q", result.RoutingState.ReasonCode)
|
|
}
|
|
if result.RoutingState.Version != 1 {
|
|
t.Fatalf("unexpected version: %d", result.RoutingState.Version)
|
|
}
|
|
}
|
|
|
|
func TestServiceEvaluateHTTPResultExplicitFailure(t *testing.T) {
|
|
repo := repository.NewMemoryRepository()
|
|
service := NewService(repo)
|
|
service.now = func() time.Time { return time.Unix(1001, 0).UTC() }
|
|
|
|
repo.UpsertRoutingState(context.Background(), domain.AccountRoutingState{
|
|
AccountID: 2,
|
|
Platform: "openai",
|
|
AccountStatus: domain.AccountStatusActive,
|
|
RoutingEnabled: true,
|
|
RiskScore: 20,
|
|
ReasonCode: "ok",
|
|
LastProbeAt: time.Unix(999, 0).UTC(),
|
|
Version: 4,
|
|
})
|
|
|
|
result, err := service.EvaluateHTTPResult(context.Background(), EvaluateInput{
|
|
AccountID: 2,
|
|
Platform: "openai",
|
|
CurrentStatus: domain.AccountStatusActive,
|
|
StatusCode: 401,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if result.Classification != domain.ProbeClassificationExplicitFailure {
|
|
t.Fatalf("unexpected classification: %q", result.Classification)
|
|
}
|
|
if result.RoutingState.AccountStatus != domain.AccountStatusSuspended {
|
|
t.Fatalf("unexpected account status: %q", result.RoutingState.AccountStatus)
|
|
}
|
|
if result.RoutingState.RoutingEnabled {
|
|
t.Fatalf("expected routing disabled")
|
|
}
|
|
if result.RoutingState.ReasonCode != "auth_rejected" {
|
|
t.Fatalf("unexpected reason code: %q", result.RoutingState.ReasonCode)
|
|
}
|
|
if result.RoutingState.Version != 2 {
|
|
t.Fatalf("unexpected version: %d", result.RoutingState.Version)
|
|
}
|
|
}
|
|
|
|
func TestServiceEvaluateHTTPResultInconclusive(t *testing.T) {
|
|
repo := repository.NewMemoryRepository()
|
|
service := NewService(repo)
|
|
service.now = func() time.Time { return time.Unix(1002, 0).UTC() }
|
|
|
|
result, err := service.EvaluateHTTPResult(context.Background(), EvaluateInput{
|
|
AccountID: 3,
|
|
Platform: "openai",
|
|
CurrentStatus: domain.AccountStatusSuspended,
|
|
TransportError: errors.New("dial tcp timeout"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if result.Classification != domain.ProbeClassificationInconclusive {
|
|
t.Fatalf("unexpected classification: %q", result.Classification)
|
|
}
|
|
if result.RoutingState.AccountStatus != domain.AccountStatusSuspended {
|
|
t.Fatalf("unexpected account status: %q", result.RoutingState.AccountStatus)
|
|
}
|
|
if result.RoutingState.RoutingEnabled {
|
|
t.Fatalf("expected routing disabled for suspended account")
|
|
}
|
|
if result.RoutingState.ReasonCode != "transport_error" {
|
|
t.Fatalf("unexpected reason code: %q", result.RoutingState.ReasonCode)
|
|
}
|
|
if result.RoutingState.RiskScore != 60 {
|
|
t.Fatalf("unexpected risk score: %d", result.RoutingState.RiskScore)
|
|
}
|
|
}
|
|
|
|
func TestServiceEvaluateHTTPResultDisablesOnlyAfterThirdExplicitFailure(t *testing.T) {
|
|
repo := repository.NewMemoryRepository()
|
|
service := NewService(repo)
|
|
service.now = func() time.Time { return time.Unix(1003, 0).UTC() }
|
|
|
|
result, err := service.EvaluateHTTPResult(context.Background(), EvaluateInput{
|
|
AccountID: 4,
|
|
Platform: "openai",
|
|
CurrentStatus: domain.AccountStatusSuspended,
|
|
StatusCode: 401,
|
|
ConsecutiveExplicitFailures: 2,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if result.RoutingState.AccountStatus != domain.AccountStatusSuspended {
|
|
t.Fatalf("expected suspended before threshold, got %q", result.RoutingState.AccountStatus)
|
|
}
|
|
|
|
result, err = service.EvaluateHTTPResult(context.Background(), EvaluateInput{
|
|
AccountID: 4,
|
|
Platform: "openai",
|
|
CurrentStatus: domain.AccountStatusSuspended,
|
|
StatusCode: 401,
|
|
ConsecutiveExplicitFailures: 3,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error on threshold failure: %v", err)
|
|
}
|
|
if result.RoutingState.AccountStatus != domain.AccountStatusDisabled {
|
|
t.Fatalf("expected disabled at threshold, got %q", result.RoutingState.AccountStatus)
|
|
}
|
|
}
|