2026-05-23 09:39:02 +08:00
package reconcile
import (
"context"
"errors"
"fmt"
"testing"
"sub2api-cn-relay-manager/internal/host/sub2api"
2026-05-23 10:55:57 +08:00
"sub2api-cn-relay-manager/internal/pack"
2026-05-23 09:39:02 +08:00
"sub2api-cn-relay-manager/internal/store/sqlite"
2026-05-25 07:30:07 +08:00
"sub2api-cn-relay-manager/internal/testutil"
2026-05-23 09:39:02 +08:00
)
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 )
}
2026-05-26 07:50:43 +08:00
if host . clearTempUnschedulableCalls != 1 {
t . Fatalf ( "clearTempUnschedulableCalls = %d, want 1" , host . clearTempUnschedulableCalls )
}
2026-05-23 09:39:02 +08:00
}
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 )
}
2026-05-26 07:50:43 +08:00
if host . clearTempUnschedulableCalls != 0 {
t . Fatalf ( "clearTempUnschedulableCalls = %d, want 0 after disable failure" , host . clearTempUnschedulableCalls )
}
2026-05-23 09:39:02 +08:00
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 )
}
}
}
2026-05-23 10:55:57 +08:00
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 )
}
}
2026-05-23 09:39:02 +08:00
type reconcileFixture struct {
hostPK int64
packPK int64
providerPK int64
batchID int64
}
func openReconcileTestStore ( t * testing . T ) * sqlite . DB {
t . Helper ( )
2026-05-25 07:30:07 +08:00
return testutil . OpenSQLiteStore ( t , testutil . SQLiteTestDSN ( t , "state.db" , true ) )
2026-05-23 09:39:02 +08:00
}
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
}
2026-05-23 10:55:57 +08:00
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 )
}
}
2026-05-23 09:39:02 +08:00
type reconcileHostStub struct {
2026-05-26 07:50:43 +08:00
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
2026-05-23 09:39:02 +08:00
}
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
}
2026-05-26 07:50:43 +08:00
func ( h * reconcileHostStub ) ClearTempUnschedulable ( _ context . Context , accountIDs [ ] string ) error {
h . clearTempUnschedulableCalls ++
h . clearedTempUnschedulableAccounts = append ( [ ] string ( nil ) , accountIDs ... )
return nil
}
2026-05-23 09:39:02 +08:00
func ( h * reconcileHostStub ) ListManagedResources ( context . Context , sub2api . ListManagedResourcesRequest ) ( sub2api . ManagedResourceSnapshot , error ) {
2026-05-23 10:55:57 +08:00
if h . listManagedResourcesErr != nil {
return sub2api . ManagedResourceSnapshot { } , h . listManagedResourcesErr
}
return h . managedResourceSnapshot , nil
2026-05-23 09:39:02 +08:00
}