Files
sub2api-cn-relay-manager/internal/reconcile/service_runtime_test.go

779 lines
27 KiB
Go

package reconcile
import (
"context"
"errors"
"fmt"
"testing"
"sub2api-cn-relay-manager/internal/host/sub2api"
"sub2api-cn-relay-manager/internal/pack"
"sub2api-cn-relay-manager/internal/store/sqlite"
"sub2api-cn-relay-manager/internal/testutil"
)
func TestRerunAccountProbesReturnsErrorForInvalidProbeSummary(t *testing.T) {
t.Parallel()
store := openReconcileTestStore(t)
defer closeReconcileTestStore(t, store)
fixture := seedReconcileFixture(t, store)
itemID := mustCreateImportBatchItem(t, store, fixture.batchID, "fp-1", "{")
svc := NewService(store, &reconcileHostStub{})
_, err := svc.rerunAccountProbes(context.Background(), []sqlite.ImportBatchItem{{
ID: itemID,
BatchID: fixture.batchID,
KeyFingerprint: "fp-1",
AccountStatus: "pending",
ProbeSummaryJSON: "{",
}}, "deepseek-chat")
if err == nil || err.Error() != fmt.Sprintf("decode import batch item %d probe summary: unexpected end of JSON input", itemID) {
t.Fatalf("rerunAccountProbes() error = %v, want decode error", err)
}
}
func TestRerunAccountProbesReturnsErrorForMissingAccountID(t *testing.T) {
t.Parallel()
store := openReconcileTestStore(t)
defer closeReconcileTestStore(t, store)
fixture := seedReconcileFixture(t, store)
itemID := mustCreateImportBatchItem(t, store, fixture.batchID, "fp-1", `{}`)
svc := NewService(store, &reconcileHostStub{})
_, err := svc.rerunAccountProbes(context.Background(), []sqlite.ImportBatchItem{{
ID: itemID,
BatchID: fixture.batchID,
KeyFingerprint: "fp-1",
AccountStatus: "pending",
ProbeSummaryJSON: `{}`,
}}, "deepseek-chat")
if err == nil || err.Error() != fmt.Sprintf("import batch item %d missing account_id in probe summary", itemID) {
t.Fatalf("rerunAccountProbes() error = %v, want missing account_id", err)
}
}
func TestRerunAccountProbesReturnsErrorWhenRetestFails(t *testing.T) {
t.Parallel()
store := openReconcileTestStore(t)
defer closeReconcileTestStore(t, store)
fixture := seedReconcileFixture(t, store)
itemID := mustCreateImportBatchItem(t, store, fixture.batchID, "fp-1", `{"account_id":"account-1"}`)
host := &reconcileHostStub{
testErrors: map[string]error{"account-1": errors.New("probe failed")},
}
svc := NewService(store, host)
_, err := svc.rerunAccountProbes(context.Background(), []sqlite.ImportBatchItem{{
ID: itemID,
BatchID: fixture.batchID,
KeyFingerprint: "fp-1",
AccountStatus: "pending",
ProbeSummaryJSON: `{"account_id":"account-1"}`,
}}, "deepseek-chat")
if err == nil || err.Error() != "re-test account account-1: probe failed" {
t.Fatalf("rerunAccountProbes() error = %v, want retest failure", err)
}
}
func TestRerunAccountProbesReturnsErrorWhenReloadModelsFails(t *testing.T) {
t.Parallel()
store := openReconcileTestStore(t)
defer closeReconcileTestStore(t, store)
fixture := seedReconcileFixture(t, store)
itemID := mustCreateImportBatchItem(t, store, fixture.batchID, "fp-1", `{"account_id":"account-1"}`)
host := &reconcileHostStub{
testResults: map[string]sub2api.ProbeResult{
"account-1": {OK: true, Status: "passed"},
},
modelErrors: map[string]error{"account-1": errors.New("models unavailable")},
}
svc := NewService(store, host)
_, err := svc.rerunAccountProbes(context.Background(), []sqlite.ImportBatchItem{{
ID: itemID,
BatchID: fixture.batchID,
KeyFingerprint: "fp-1",
AccountStatus: "pending",
ProbeSummaryJSON: `{"account_id":"account-1"}`,
}}, "deepseek-chat")
if err == nil || err.Error() != "reload account models account-1: models unavailable" {
t.Fatalf("rerunAccountProbes() error = %v, want model reload failure", err)
}
}
func TestRerunAccountProbesPersistsWarningsAndDeduplicatesSuspectAccounts(t *testing.T) {
t.Parallel()
store := openReconcileTestStore(t)
defer closeReconcileTestStore(t, store)
fixture := seedReconcileFixture(t, store)
itemID1 := mustCreateImportBatchItem(t, store, fixture.batchID, "fp-1", `{"account_id":"account-1"}`)
itemID2 := mustCreateImportBatchItem(t, store, fixture.batchID, "fp-2", `{"account_id":" account-1 "}`)
host := &reconcileHostStub{
testResults: map[string]sub2api.ProbeResult{
"account-1": {
OK: false,
Status: "failed",
Message: "API returned 403: Forbidden",
},
},
models: map[string][]sub2api.AccountModel{
"account-1": {{ID: "deepseek-chat"}},
},
}
svc := NewService(store, host)
summary, err := svc.rerunAccountProbes(context.Background(), []sqlite.ImportBatchItem{
{
ID: itemID1,
BatchID: fixture.batchID,
KeyFingerprint: "fp-1",
AccountStatus: "pending",
ProbeSummaryJSON: `{"account_id":"account-1"}`,
},
{
ID: itemID2,
BatchID: fixture.batchID,
KeyFingerprint: "fp-2",
AccountStatus: "pending",
ProbeSummaryJSON: `{"account_id":" account-1 "}`,
},
}, "deepseek-chat")
if err != nil {
t.Fatalf("rerunAccountProbes() error = %v", err)
}
if summary.Failures != 0 {
t.Fatalf("summary.Failures = %d, want 0 for advisory warning", summary.Failures)
}
if !summary.ResponsesCapabilitySuspect {
t.Fatal("summary.ResponsesCapabilitySuspect = false, want true")
}
if len(summary.AccountIDs) != 1 || summary.AccountIDs[0] != "account-1" {
t.Fatalf("summary.AccountIDs = %v, want [account-1]", summary.AccountIDs)
}
for _, itemID := range []int64{itemID1, itemID2} {
items, err := store.ImportBatchItems().GetByBatchID(context.Background(), fixture.batchID)
if err != nil {
t.Fatalf("ImportBatchItems().GetByBatchID() error = %v", err)
}
var got sqlite.ImportBatchItem
for _, item := range items {
if item.ID == itemID {
got = item
break
}
}
if got.AccountStatus != accountStatusWarning {
t.Fatalf("item %d AccountStatus = %q, want %q", itemID, got.AccountStatus, accountStatusWarning)
}
probes, err := store.ProbeResults().GetByBatchItemID(context.Background(), itemID)
if err != nil {
t.Fatalf("ProbeResults().GetByBatchItemID(%d) error = %v", itemID, err)
}
if len(probes) != 1 || probes[0].Status != accountStatusWarning {
t.Fatalf("probe results for item %d = %+v, want single warning result", itemID, probes)
}
}
}
func TestRerunAccessClosureReturnsPreviousStatusWithoutProbeAPIKey(t *testing.T) {
t.Parallel()
store := openReconcileTestStore(t)
defer closeReconcileTestStore(t, store)
fixture := seedReconcileFixture(t, store)
closures := []sqlite.AccessClosureRecord{{
BatchID: fixture.batchID,
ClosureType: accessModeSelfService,
Status: accessStatusSelfServiceReady,
}}
status, checked, err := NewService(store, &reconcileHostStub{}).rerunAccessClosure(
context.Background(),
fixture.batchID,
closures,
"",
"deepseek-chat",
nil,
false,
)
if err != nil {
t.Fatalf("rerunAccessClosure() error = %v", err)
}
if checked {
t.Fatal("checked = true, want false without probe api key")
}
if status != accessStatusSelfServiceReady {
t.Fatalf("status = %q, want %q", status, accessStatusSelfServiceReady)
}
}
func TestRerunAccessClosureReturnsErrorWhenGatewayCheckFails(t *testing.T) {
t.Parallel()
store := openReconcileTestStore(t)
defer closeReconcileTestStore(t, store)
fixture := seedReconcileFixture(t, store)
closures := mustCreateAndLoadAccessClosures(t, store, fixture.batchID, sqlite.AccessClosureRecord{
BatchID: fixture.batchID,
ClosureType: accessModeSelfService,
Status: accessStatusSelfServiceReady,
})
host := &reconcileHostStub{gatewayErr: errors.New("gateway down")}
_, _, err := NewService(store, host).rerunAccessClosure(context.Background(), fixture.batchID, closures, "user-key", "deepseek-chat", nil, false)
if err == nil || err.Error() != "re-check gateway access: gateway down" {
t.Fatalf("rerunAccessClosure() error = %v, want gateway failure", err)
}
}
func TestRerunAccessClosureReturnsErrorWhenCompletionCheckFails(t *testing.T) {
t.Parallel()
store := openReconcileTestStore(t)
defer closeReconcileTestStore(t, store)
fixture := seedReconcileFixture(t, store)
closures := mustCreateAndLoadAccessClosures(t, store, fixture.batchID, sqlite.AccessClosureRecord{
BatchID: fixture.batchID,
ClosureType: accessModeSelfService,
Status: accessStatusSelfServiceReady,
})
host := &reconcileHostStub{
gatewayResult: sub2api.GatewayAccessResult{OK: true, HasExpectedModel: true},
completionErrs: []error{
errors.New("completion failed"),
},
}
_, _, err := NewService(store, host).rerunAccessClosure(context.Background(), fixture.batchID, closures, "user-key", "deepseek-chat", nil, false)
if err == nil || err.Error() != "re-check gateway completion: completion failed" {
t.Fatalf("rerunAccessClosure() error = %v, want completion failure", err)
}
}
func TestRerunAccessClosureReturnsErrorWhenCompletionAfterRepairFails(t *testing.T) {
t.Parallel()
store := openReconcileTestStore(t)
defer closeReconcileTestStore(t, store)
fixture := seedReconcileFixture(t, store)
closures := mustCreateAndLoadAccessClosures(t, store, fixture.batchID, sqlite.AccessClosureRecord{
BatchID: fixture.batchID,
ClosureType: accessModeSelfService,
Status: accessStatusSelfServiceReady,
})
host := &reconcileHostStub{
gatewayResult: sub2api.GatewayAccessResult{OK: true, HasExpectedModel: true},
completionResults: []sub2api.GatewayCompletionResult{
{OK: false, StatusCode: 502, BodyPreview: `{"error":{"message":"No available accounts"}}`},
},
completionErrs: []error{
nil,
errors.New("still failing"),
},
}
_, _, err := NewService(store, host).rerunAccessClosure(context.Background(), fixture.batchID, closures, "user-key", "deepseek-chat", []string{"account-1"}, true)
if err == nil || err.Error() != "re-check gateway completion after capability repair: still failing" {
t.Fatalf("rerunAccessClosure() error = %v, want post-repair completion failure", err)
}
if host.clearTempUnschedulableCalls != 1 {
t.Fatalf("clearTempUnschedulableCalls = %d, want 1", host.clearTempUnschedulableCalls)
}
}
func TestRerunAccessClosurePersistsBrokenRecordWhenRepairCannotRun(t *testing.T) {
t.Parallel()
store := openReconcileTestStore(t)
defer closeReconcileTestStore(t, store)
fixture := seedReconcileFixture(t, store)
closures := mustCreateAndLoadAccessClosures(t, store, fixture.batchID, sqlite.AccessClosureRecord{
BatchID: fixture.batchID,
ClosureType: accessModeSelfService,
Status: accessStatusSelfServiceReady,
})
host := &reconcileHostStub{
gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
completionResults: []sub2api.GatewayCompletionResult{
{OK: false, StatusCode: 502, ContentType: "application/json", BodyPreview: `{"error":{"message":"Upstream service temporarily unavailable"}}`},
},
disableResponsesErr: errors.New("host rejected update"),
}
status, checked, err := NewService(store, host).rerunAccessClosure(context.Background(), fixture.batchID, closures, "user-key", "deepseek-chat", []string{"account-1"}, true)
if err != nil {
t.Fatalf("rerunAccessClosure() error = %v", err)
}
if !checked {
t.Fatal("checked = false, want true")
}
if status != accessStatusBroken {
t.Fatalf("status = %q, want %q", status, accessStatusBroken)
}
if host.disableResponsesCalls != 1 {
t.Fatalf("disableResponsesCalls = %d, want 1", host.disableResponsesCalls)
}
if host.clearTempUnschedulableCalls != 0 {
t.Fatalf("clearTempUnschedulableCalls = %d, want 0 after disable failure", host.clearTempUnschedulableCalls)
}
if host.completionCalls != 1 {
t.Fatalf("completionCalls = %d, want 1 without retry after disable failure", host.completionCalls)
}
records, err := store.AccessClosures().GetByBatchID(context.Background(), fixture.batchID)
if err != nil {
t.Fatalf("AccessClosures().GetByBatchID() error = %v", err)
}
if len(records) != 2 {
t.Fatalf("access closure records = %d, want 2", len(records))
}
if records[1].Status != accessStatusBroken {
t.Fatalf("persisted access closure status = %q, want %q", records[1].Status, accessStatusBroken)
}
}
func TestStoredResourcesForReconcileMergesCurrentBatchAndSharedScaffoldingOnly(t *testing.T) {
t.Parallel()
store := openReconcileTestStore(t)
defer closeReconcileTestStore(t, store)
fixture := seedReconcileFixture(t, store)
otherBatchID := mustCreateImportBatch(t, store, fixture.hostPK, fixture.packPK, fixture.providerPK, "partial", "succeeded", "self_service_ready")
mustCreateManagedResource(t, store, fixture.batchID, fixture.hostPK, "group", "group-1", "group one")
mustCreateManagedResource(t, store, fixture.batchID, fixture.hostPK, "account", "account-1", "account one")
mustCreateManagedResource(t, store, otherBatchID, fixture.hostPK, "group", "group-2", "shared group")
mustCreateManagedResource(t, store, otherBatchID, fixture.hostPK, "channel", "channel-2", "shared channel")
mustCreateManagedResource(t, store, otherBatchID, fixture.hostPK, "plan", "plan-2", "shared plan")
mustCreateManagedResource(t, store, otherBatchID, fixture.hostPK, "account", "account-2", "shared account should not merge")
got, err := NewService(store, &reconcileHostStub{}).storedResourcesForReconcile(context.Background(), fixture.providerPK, fixture.hostPK, fixture.batchID)
if err != nil {
t.Fatalf("storedResourcesForReconcile() error = %v", err)
}
if len(got) != 5 {
t.Fatalf("storedResourcesForReconcile() len = %d, want 5; values = %+v", len(got), got)
}
want := map[string]bool{
"group:group-1": false,
"group:group-2": false,
"account:account-1": false,
"channel:channel-2": false,
"plan:plan-2": false,
}
for _, resource := range got {
key := resource.ResourceType + ":" + resource.HostResourceID
if _, ok := want[key]; !ok {
t.Fatalf("unexpected merged resource %q in %+v", key, got)
}
want[key] = true
}
for key, seen := range want {
if !seen {
t.Fatalf("missing merged resource %q in %+v", key, got)
}
}
}
func TestReconcileValidatesTopLevelRequest(t *testing.T) {
t.Parallel()
req := Request{}
if _, err := NewService(nil, &reconcileHostStub{}).Reconcile(context.Background(), req); err == nil || err.Error() != "store is required" {
t.Fatalf("Reconcile(nil store) error = %v, want store is required", err)
}
store := openReconcileTestStore(t)
defer closeReconcileTestStore(t, store)
if _, err := NewService(store, nil).Reconcile(context.Background(), req); err == nil || err.Error() != "host adapter is required" {
t.Fatalf("Reconcile(nil host) error = %v, want host adapter is required", err)
}
if _, err := NewService(store, &reconcileHostStub{}).Reconcile(context.Background(), req); err == nil || err.Error() != "host_id is required" {
t.Fatalf("Reconcile(missing host_id) error = %v, want host_id is required", err)
}
if _, err := NewService(store, &reconcileHostStub{}).Reconcile(context.Background(), Request{HostID: "host-1"}); err == nil || err.Error() != "host_base_url is required" {
t.Fatalf("Reconcile(missing host_base_url) error = %v, want host_base_url is required", err)
}
}
func TestReconcileRejectsNonReconcilableLatestBatch(t *testing.T) {
t.Parallel()
store := openReconcileTestStore(t)
defer closeReconcileTestStore(t, store)
fixture := seedReconcileFixture(t, store)
mustExecReconcileSQL(t, store, `UPDATE import_batches SET batch_status = 'failed' WHERE id = ?`, fixture.batchID)
_, err := NewService(store, &reconcileHostStub{}).Reconcile(context.Background(), Request{
HostID: "host-1",
HostBaseURL: "https://sub2api.example.com",
AccessProbeAPIKey: "user-key",
Pack: pack.LoadedPack{
Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api"},
},
Provider: pack.ProviderManifest{
ProviderID: "deepseek",
SmokeTestModel: "deepseek-chat",
},
})
if err == nil || err.Error() != "latest import batch is failed; run import again before reconcile" {
t.Fatalf("Reconcile() error = %v, want non-reconcilable batch error", err)
}
}
func TestReconcilePersistsActiveRunForHealthySnapshot(t *testing.T) {
t.Parallel()
store := openReconcileTestStore(t)
defer closeReconcileTestStore(t, store)
fixture := seedReconcileFixture(t, store)
mustCreateImportBatchItem(t, store, fixture.batchID, "fp-1", `{"account_id":"account-1"}`)
mustCreateManagedResource(t, store, fixture.batchID, fixture.hostPK, "group", "group-1", "group one")
mustCreateManagedResource(t, store, fixture.batchID, fixture.hostPK, "account", "account-1", "account one")
mustCreateAndLoadAccessClosures(t, store, fixture.batchID, sqlite.AccessClosureRecord{
BatchID: fixture.batchID,
ClosureType: accessModeSelfService,
Status: accessStatusSelfServiceReady,
})
host := &reconcileHostStub{
testResults: map[string]sub2api.ProbeResult{
"account-1": {OK: true, Status: accountStatusPassed, Message: "ok"},
},
models: map[string][]sub2api.AccountModel{
"account-1": {{ID: "deepseek-chat"}},
},
gatewayResult: sub2api.GatewayAccessResult{
OK: true,
StatusCode: 200,
HasExpectedModel: true,
CompletionOK: true,
Models: []string{"deepseek-chat"},
},
completionResults: []sub2api.GatewayCompletionResult{
{OK: true, StatusCode: 200},
},
managedResourceSnapshot: sub2api.ManagedResourceSnapshot{
Groups: []sub2api.NamedResource{{ID: "group-1", Name: "group one"}},
Accounts: []sub2api.NamedResource{{ID: "account-1", Name: "account one"}},
},
}
result, err := NewService(store, host).Reconcile(context.Background(), Request{
HostID: "host-1",
HostBaseURL: "https://sub2api.example.com",
AccessProbeAPIKey: "user-key",
Pack: pack.LoadedPack{
Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api"},
},
Provider: pack.ProviderManifest{
ProviderID: "deepseek",
SmokeTestModel: "deepseek-chat",
},
})
if err != nil {
t.Fatalf("Reconcile() error = %v", err)
}
if result.Status != "active" {
t.Fatalf("result.Status = %q, want active", result.Status)
}
if result.MissingCount != 0 || result.ExtraCount != 0 || result.ProbeFailureCount != 0 {
t.Fatalf("result = %+v, want no drift and no probe failures", result)
}
if result.AccessStatus != accessStatusSelfServiceReady {
t.Fatalf("result.AccessStatus = %q, want %q", result.AccessStatus, accessStatusSelfServiceReady)
}
runs, err := store.ReconcileRuns().GetByBatchID(context.Background(), fixture.batchID)
if err != nil {
t.Fatalf("ReconcileRuns().GetByBatchID() error = %v", err)
}
if len(runs) != 1 || runs[0].Status != "active" {
t.Fatalf("persisted reconcile runs = %+v, want single active run", runs)
}
}
type reconcileFixture struct {
hostPK int64
packPK int64
providerPK int64
batchID int64
}
func openReconcileTestStore(t *testing.T) *sqlite.DB {
t.Helper()
return testutil.OpenSQLiteStore(t, testutil.SQLiteTestDSN(t, "state.db", true))
}
func closeReconcileTestStore(t *testing.T, store *sqlite.DB) {
t.Helper()
if err := store.Close(); err != nil {
t.Fatalf("store.Close() error = %v", err)
}
}
func seedReconcileFixture(t *testing.T, store *sqlite.DB) reconcileFixture {
t.Helper()
hostPK, err := store.Hosts().Create(context.Background(), sqlite.Host{
HostID: "host-1",
BaseURL: "https://sub2api.example.com",
HostVersion: "0.1.126",
AuthType: "apikey",
AuthToken: "test-token",
})
if err != nil {
t.Fatalf("Hosts().Create() error = %v", err)
}
packPK, err := store.Packs().Create(context.Background(), sqlite.Pack{
PackID: "openai-cn-pack",
Version: "1.0.0",
Checksum: "checksum-1",
})
if err != nil {
t.Fatalf("Packs().Create() error = %v", err)
}
providerPK, err := store.Providers().Create(context.Background(), sqlite.Provider{
PackID: packPK,
ProviderID: "deepseek",
DisplayName: "DeepSeek",
BaseURL: "https://api.example.com",
Platform: "openai",
AccountType: "openai",
SmokeTestModel: "deepseek-chat",
})
if err != nil {
t.Fatalf("Providers().Create() error = %v", err)
}
batchID := mustCreateImportBatch(t, store, hostPK, packPK, providerPK, "partial", "partially_succeeded", "self_service_ready")
return reconcileFixture{
hostPK: hostPK,
packPK: packPK,
providerPK: providerPK,
batchID: batchID,
}
}
func mustCreateImportBatch(t *testing.T, store *sqlite.DB, hostPK, packPK, providerPK int64, mode, batchStatus, accessStatus string) int64 {
t.Helper()
id, err := store.ImportBatches().Create(context.Background(), sqlite.ImportBatch{
HostID: hostPK,
PackID: packPK,
ProviderID: providerPK,
Mode: mode,
BatchStatus: batchStatus,
AccessStatus: accessStatus,
})
if err != nil {
t.Fatalf("ImportBatches().Create() error = %v", err)
}
return id
}
func mustCreateImportBatchItem(t *testing.T, store *sqlite.DB, batchID int64, fingerprint, probeSummary string) int64 {
t.Helper()
id, err := store.ImportBatchItems().Create(context.Background(), sqlite.ImportBatchItem{
BatchID: batchID,
KeyFingerprint: fingerprint,
AccountStatus: "pending",
ProbeSummaryJSON: probeSummary,
})
if err != nil {
t.Fatalf("ImportBatchItems().Create() error = %v", err)
}
return id
}
func mustCreateManagedResource(t *testing.T, store *sqlite.DB, batchID, hostPK int64, resourceType, hostResourceID, resourceName string) int64 {
t.Helper()
id, err := store.ManagedResources().Create(context.Background(), sqlite.ManagedResource{
BatchID: batchID,
HostID: hostPK,
ResourceType: resourceType,
HostResourceID: hostResourceID,
ResourceName: resourceName,
})
if err != nil {
t.Fatalf("ManagedResources().Create() error = %v", err)
}
return id
}
func mustCreateAndLoadAccessClosures(t *testing.T, store *sqlite.DB, batchID int64, records ...sqlite.AccessClosureRecord) []sqlite.AccessClosureRecord {
t.Helper()
for _, record := range records {
if _, err := store.AccessClosures().Create(context.Background(), record); err != nil {
t.Fatalf("AccessClosures().Create() error = %v", err)
}
}
loaded, err := store.AccessClosures().GetByBatchID(context.Background(), batchID)
if err != nil {
t.Fatalf("AccessClosures().GetByBatchID() error = %v", err)
}
return loaded
}
func mustExecReconcileSQL(t *testing.T, store *sqlite.DB, query string, args ...any) {
t.Helper()
if _, err := store.SQLDB().Exec(query, args...); err != nil {
t.Fatalf("Exec(%q) error = %v", query, err)
}
}
type reconcileHostStub struct {
disableResponsesErr error
gatewayErr error
gatewayResult sub2api.GatewayAccessResult
testResults map[string]sub2api.ProbeResult
testErrors map[string]error
models map[string][]sub2api.AccountModel
modelErrors map[string]error
completionResults []sub2api.GatewayCompletionResult
completionErrs []error
completionCalls int
disableResponsesCalls int
disabledResponsesAccounts []string
clearTempUnschedulableCalls int
clearedTempUnschedulableAccounts []string
managedResourceSnapshot sub2api.ManagedResourceSnapshot
listManagedResourcesErr error
}
func (h *reconcileHostStub) GetHostVersion(context.Context) (string, error) {
return "0.1.126", nil
}
func (h *reconcileHostStub) ProbeCapabilities(context.Context) (sub2api.HostCapabilities, error) {
return sub2api.HostCapabilities{}, nil
}
func (h *reconcileHostStub) CreateGroup(context.Context, sub2api.CreateGroupRequest) (sub2api.GroupRef, error) {
return sub2api.GroupRef{}, nil
}
func (h *reconcileHostStub) DeleteGroup(context.Context, string) error {
return nil
}
func (h *reconcileHostStub) CreateChannel(context.Context, sub2api.CreateChannelRequest) (sub2api.ChannelRef, error) {
return sub2api.ChannelRef{}, nil
}
func (h *reconcileHostStub) UpdateChannel(context.Context, string, sub2api.CreateChannelRequest) error {
return nil
}
func (h *reconcileHostStub) DeleteChannel(context.Context, string) error {
return nil
}
func (h *reconcileHostStub) CreatePlan(context.Context, sub2api.CreatePlanRequest) (sub2api.PlanRef, error) {
return sub2api.PlanRef{}, nil
}
func (h *reconcileHostStub) DeletePlan(context.Context, string) error {
return nil
}
func (h *reconcileHostStub) CreateAccount(context.Context, sub2api.CreateAccountRequest) (sub2api.AccountRef, error) {
return sub2api.AccountRef{}, nil
}
func (h *reconcileHostStub) BatchCreateAccounts(context.Context, sub2api.BatchCreateAccountsRequest) ([]sub2api.AccountRef, error) {
return nil, nil
}
func (h *reconcileHostStub) DeleteAccount(context.Context, string) error {
return nil
}
func (h *reconcileHostStub) TestAccount(_ context.Context, accountID, _ string) (sub2api.ProbeResult, error) {
if err, ok := h.testErrors[accountID]; ok {
return sub2api.ProbeResult{}, err
}
if result, ok := h.testResults[accountID]; ok {
return result, nil
}
return sub2api.ProbeResult{}, fmt.Errorf("missing test result for %s", accountID)
}
func (h *reconcileHostStub) GetAccountModels(_ context.Context, accountID string) ([]sub2api.AccountModel, error) {
if err, ok := h.modelErrors[accountID]; ok {
return nil, err
}
if models, ok := h.models[accountID]; ok {
return models, nil
}
return nil, fmt.Errorf("missing models for %s", accountID)
}
func (h *reconcileHostStub) EnsureSubscriptionAccess(context.Context, sub2api.EnsureSubscriptionAccessRequest) (sub2api.SubscriptionAccessRef, error) {
return sub2api.SubscriptionAccessRef{}, nil
}
func (h *reconcileHostStub) AssignSubscription(context.Context, sub2api.AssignSubscriptionRequest) (sub2api.SubscriptionRef, error) {
return sub2api.SubscriptionRef{}, nil
}
func (h *reconcileHostStub) CheckGatewayAccess(_ context.Context, _ sub2api.GatewayAccessCheckRequest) (sub2api.GatewayAccessResult, error) {
if h.gatewayErr != nil {
return sub2api.GatewayAccessResult{}, h.gatewayErr
}
return h.gatewayResult, nil
}
func (h *reconcileHostStub) CheckGatewayCompletion(_ context.Context, _ sub2api.GatewayCompletionCheckRequest) (sub2api.GatewayCompletionResult, error) {
idx := h.completionCalls
h.completionCalls++
if idx < len(h.completionErrs) && h.completionErrs[idx] != nil {
return sub2api.GatewayCompletionResult{}, h.completionErrs[idx]
}
if len(h.completionResults) == 0 {
return sub2api.GatewayCompletionResult{}, nil
}
if idx >= len(h.completionResults) {
idx = len(h.completionResults) - 1
}
return h.completionResults[idx], nil
}
func (h *reconcileHostStub) DisableOpenAIResponsesAPI(_ context.Context, accountIDs []string) error {
h.disableResponsesCalls++
h.disabledResponsesAccounts = append([]string(nil), accountIDs...)
return h.disableResponsesErr
}
func (h *reconcileHostStub) ClearTempUnschedulable(_ context.Context, accountIDs []string) error {
h.clearTempUnschedulableCalls++
h.clearedTempUnschedulableAccounts = append([]string(nil), accountIDs...)
return nil
}
func (h *reconcileHostStub) ListManagedResources(context.Context, sub2api.ListManagedResourcesRequest) (sub2api.ManagedResourceSnapshot, error) {
if h.listManagedResourcesErr != nil {
return sub2api.ManagedResourceSnapshot{}, h.listManagedResourcesErr
}
return h.managedResourceSnapshot, nil
}