package access import ( "context" "errors" "testing" "sub2api-cn-relay-manager/internal/host/sub2api" ) func TestValidateRejectsMissingProbeAPIKeyForSelfService(t *testing.T) { err := Validate(ClosureRequest{Mode: "self_service"}) if err == nil { t.Fatal("Validate() error = nil, want validation error") } } func TestValidateRejectsMissingSubscriptionsForSubscriptionMode(t *testing.T) { err := Validate(ClosureRequest{Mode: "subscription", ProbeAPIKey: "user-key"}) if err == nil { t.Fatal("Validate() error = nil, want validation error") } } func TestValidateAllowsManagedSubscriptionProbeWithoutExplicitAPIKey(t *testing.T) { err := Validate(ClosureRequest{ Mode: "subscription", GroupID: "group-1", ExpectedModel: "deepseek-chat", Subscriptions: []SubscriptionTarget{{UserID: "crm-user-42", DurationDays: 30}}, }) if err != nil { t.Fatalf("Validate() error = %v, want nil for managed subscription probe", err) } } func TestServiceCloseAssignsSubscriptionsAndProbesGateway(t *testing.T) { host := &fakeClosureHost{ gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}}, managedAccess: map[string]sub2api.SubscriptionAccessRef{ "user-1": {UserID: "host-user-1", APIKey: "managed-user-key"}, }, } service := NewService(host) result, err := service.Close(context.Background(), ClosureRequest{ Mode: "subscription", GroupID: "group-1", ExpectedModel: "deepseek-chat", Subscriptions: []SubscriptionTarget{{UserID: "user-1", DurationDays: 30}}, }) if err != nil { t.Fatalf("Close() error = %v", err) } if len(host.assigned) != 1 { t.Fatalf("assigned subscriptions = %d, want 1", len(host.assigned)) } if host.assigned[0].UserID != "host-user-1" { t.Fatalf("assigned subscription user = %q, want host-user-1", host.assigned[0].UserID) } if host.gatewayProbe.APIKey != "managed-user-key" || host.gatewayProbe.ExpectedModel != "deepseek-chat" { t.Fatalf("gateway probe = %+v, want api key + expected model", host.gatewayProbe) } if !result.OK || !result.HasExpectedModel { t.Fatalf("gateway result = %+v, want success", result) } } func TestServiceCloseReturnsSubscriptionErrorBeforeGatewayProbe(t *testing.T) { host := &fakeClosureHost{assignErr: errors.New("assign failed")} service := NewService(host) _, err := service.Close(context.Background(), ClosureRequest{ Mode: "subscription", ProbeAPIKey: "user-key", GroupID: "group-1", ExpectedModel: "deepseek-chat", Subscriptions: []SubscriptionTarget{{UserID: "user-1", DurationDays: 30}}, }) if err == nil { t.Fatal("Close() error = nil, want subscription failure") } if host.gatewayProbe.APIKey != "" { t.Fatalf("gateway probe should not run after subscription error, got %+v", host.gatewayProbe) } } type fakeClosureHost struct { assigned []sub2api.AssignSubscriptionRequest managedAccess map[string]sub2api.SubscriptionAccessRef assignErr error gatewayProbe sub2api.GatewayAccessCheckRequest gatewayResult sub2api.GatewayAccessResult gatewayErr error } func (f *fakeClosureHost) EnsureSubscriptionAccess(_ context.Context, req sub2api.EnsureSubscriptionAccessRequest) (sub2api.SubscriptionAccessRef, error) { if ref, ok := f.managedAccess[req.UserSelector]; ok { return ref, nil } return sub2api.SubscriptionAccessRef{}, errors.New("missing managed access") } func (f *fakeClosureHost) AssignSubscription(_ context.Context, req sub2api.AssignSubscriptionRequest) (sub2api.SubscriptionRef, error) { if f.assignErr != nil { return sub2api.SubscriptionRef{}, f.assignErr } f.assigned = append(f.assigned, req) return sub2api.SubscriptionRef{ID: "sub-1"}, nil } func (f *fakeClosureHost) CheckGatewayAccess(_ context.Context, req sub2api.GatewayAccessCheckRequest) (sub2api.GatewayAccessResult, error) { f.gatewayProbe = req if f.gatewayErr != nil { return sub2api.GatewayAccessResult{}, f.gatewayErr } return f.gatewayResult, nil }