fix(provision): reconcile channel pricing and hosted access
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user