fix(provision): reconcile channel pricing and hosted access

This commit is contained in:
phamnazage-jpg
2026-05-20 22:09:40 +08:00
parent 83ee216a4d
commit ca1d448cc0
27 changed files with 1344 additions and 154 deletions

View File

@@ -5,10 +5,12 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
"time"
@@ -874,6 +876,47 @@ func TestHandlerErrorPaths(t *testing.T) {
}
}
func TestResolveLatestAccessStatusAggregatesAcrossModeBatches(t *testing.T) {
store := openAppTestStore(t)
defer closeAppTestStore(t, store)
ctx := context.Background()
hostID, err := store.Hosts().Create(ctx, sqlite.Host{HostID: "host-1", BaseURL: "https://sub2api.example.com", HostVersion: "0.1.126", AuthType: "apikey", AuthToken: "token"})
if err != nil {
t.Fatalf("Hosts().Create() error = %v", err)
}
packID, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", Checksum: "checksum-1"})
if err != nil {
t.Fatalf("Packs().Create() error = %v", err)
}
providerID, err := store.Providers().Create(ctx, sqlite.Provider{PackID: packID, ProviderID: "deepseek", DisplayName: "DeepSeek", BaseURL: "https://api.deepseek.com", Platform: "openai"})
if err != nil {
t.Fatalf("Providers().Create() error = %v", err)
}
batchSubscription, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{HostID: hostID, PackID: packID, ProviderID: providerID, Mode: provision.ImportModePartial, BatchStatus: provision.BatchStatusSucceeded, AccessStatus: provision.AccessStatusSubscriptionReady})
if err != nil {
t.Fatalf("ImportBatches().Create(subscription) error = %v", err)
}
if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{BatchID: batchSubscription, ClosureType: provision.AccessModeSubscription, Status: provision.AccessStatusSubscriptionReady, DetailsJSON: "{}"}); err != nil {
t.Fatalf("AccessClosures().Create(subscription) error = %v", err)
}
batchSelf, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{HostID: hostID, PackID: packID, ProviderID: providerID, Mode: provision.ImportModePartial, BatchStatus: provision.BatchStatusSucceeded, AccessStatus: provision.AccessStatusSelfServiceReady})
if err != nil {
t.Fatalf("ImportBatches().Create(self_service) error = %v", err)
}
if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{BatchID: batchSelf, ClosureType: provision.AccessModeSelfService, Status: provision.AccessStatusSelfServiceReady, DetailsJSON: "{}"}); err != nil {
t.Fatalf("AccessClosures().Create(self_service) error = %v", err)
}
got, err := resolveLatestAccessStatus(ctx, store, sqlite.Provider{ID: providerID, ProviderID: "deepseek"}, "host-1")
if err != nil {
t.Fatalf("resolveLatestAccessStatus() error = %v", err)
}
if got != provision.AccessStatusFullyReady {
t.Fatalf("resolveLatestAccessStatus() = %q, want %q", got, provision.AccessStatusFullyReady)
}
}
func TestProviderAccessStatusMultipleClosures(t *testing.T) {
handler := NewAPIHandler("t", ActionSet{
GetProviderAccessStatus: func(context.Context, ProviderQueryRequest) (provision.ProviderSnapshot, error) {
@@ -926,6 +969,24 @@ func TestHostSupportStatusRequiresPlansCapability(t *testing.T) {
}
}
func openAppTestStore(t *testing.T) *sqlite.DB {
t.Helper()
dbPath := filepath.Join(t.TempDir(), "state.db")
dsn := fmt.Sprintf("file:%s?_busy_timeout=5000&_pragma=foreign_keys(0)", filepath.ToSlash(dbPath))
store, err := sqlite.Open(context.Background(), dsn)
if err != nil {
t.Fatalf("sqlite.Open() error = %v", err)
}
return store
}
func closeAppTestStore(t *testing.T, store *sqlite.DB) {
t.Helper()
if err := store.Close(); err != nil {
t.Fatalf("store.Close() error = %v", err)
}
}
func assertJSONContains(t *testing.T, payload []byte, key string, want any) {
t.Helper()
var decoded map[string]any

View File

@@ -1367,37 +1367,10 @@ func NewActionSet(sqliteDSN string) ActionSet {
return AccessPreviewResult{}, fmt.Errorf("provider %q exists in multiple packs; pack_id is required", req.ProviderID)
}
providerRow := providers[0]
if strings.TrimSpace(req.HostID) != "" {
hostRow, err := store.Hosts().GetByHostID(ctx, req.HostID)
if err != nil {
return AccessPreviewResult{}, err
}
batch, err := store.ImportBatches().GetLatestByProviderIDAndHostID(ctx, providerRow.ID, hostRow.ID)
if err != nil {
return AccessPreviewResult{}, fmt.Errorf("find batch for provider: %w", err)
}
latestStatus := batch.AccessStatus
closures, err := store.AccessClosures().GetByBatchID(ctx, batch.ID)
if err == nil && len(closures) > 0 {
latestStatus = closures[len(closures)-1].Status
}
available := accessStatusSupportsMode(latestStatus, req.Mode)
message := fmt.Sprintf("latest access status: %s", latestStatus)
if !available {
message = fmt.Sprintf("access status %s does not satisfy mode %s", latestStatus, req.Mode)
}
return AccessPreviewResult{ProviderID: req.ProviderID, Mode: req.Mode, Available: available, Message: message}, nil
}
batch, err := store.ImportBatches().GetLatestByProviderID(ctx, providerRow.ID)
latestStatus, err := resolveLatestAccessStatus(ctx, store, providerRow, req.HostID)
if err != nil {
return AccessPreviewResult{}, fmt.Errorf("find batch for provider: %w", err)
}
latestStatus := batch.AccessStatus
closures, err := store.AccessClosures().GetByBatchID(ctx, batch.ID)
if err == nil && len(closures) > 0 {
latestStatus = closures[len(closures)-1].Status
}
available := accessStatusSupportsMode(latestStatus, req.Mode)
message := fmt.Sprintf("latest access status: %s", latestStatus)
if !available {
@@ -1440,6 +1413,45 @@ func resolveProvidersForQuery(ctx context.Context, store *sqlite.DB, req Provide
return store.Providers().ListByProviderID(ctx, providerID)
}
func resolveLatestAccessStatus(ctx context.Context, store *sqlite.DB, providerRow sqlite.Provider, hostID string) (string, error) {
if store == nil {
return "", fmt.Errorf("store is required")
}
if strings.TrimSpace(hostID) != "" {
hostRow, err := store.Hosts().GetByHostID(ctx, hostID)
if err != nil {
return "", err
}
batches, err := store.ImportBatches().ListByProviderIDAndHostID(ctx, providerRow.ID, hostRow.ID)
if err != nil {
return "", err
}
modeStatuses, err := provision.LatestModeAccessStatuses(ctx, store, batches)
if err != nil {
return "", err
}
return provision.AggregateAccessStatus(modeStatuses), nil
}
batches, err := store.ImportBatches().ListByProviderID(ctx, providerRow.ID)
if err != nil {
return "", err
}
if len(batches) == 0 {
return "", fmt.Errorf("latest import batch not found for provider")
}
hostIDValue := batches[0].HostID
for _, batch := range batches[1:] {
if batch.HostID != hostIDValue {
return "", fmt.Errorf("provider exists on multiple hosts; host_id is required")
}
}
modeStatuses, err := provision.LatestModeAccessStatuses(ctx, store, batches)
if err != nil {
return "", err
}
return provision.AggregateAccessStatus(modeStatuses), nil
}
func resolveManagedHost(ctx context.Context, store *sqlite.DB, hostID, baseURL string, auth CreateHostAuth) (sqlite.Host, *sub2api.Client, error) {
if store == nil {
return sqlite.Host{}, nil, fmt.Errorf("store is required")