chore: establish local platform baseline

This commit is contained in:
Codex Local
2026-08-26 06:41:54 +08:00
commit 29bdf4900f
575 changed files with 95644 additions and 0 deletions
+549
View File
@@ -0,0 +1,549 @@
package app
import (
"context"
"fmt"
"log/slog"
"net/http"
"os"
"time"
"data-terminal-cloud/internal/alertnotification"
"data-terminal-cloud/internal/alerts"
"data-terminal-cloud/internal/audit"
"data-terminal-cloud/internal/billing"
"data-terminal-cloud/internal/cache"
"data-terminal-cloud/internal/config"
"data-terminal-cloud/internal/contentpolicy"
"data-terminal-cloud/internal/database"
"data-terminal-cloud/internal/dataexport"
"data-terminal-cloud/internal/device"
"data-terminal-cloud/internal/deviceauth"
"data-terminal-cloud/internal/diagnostics"
"data-terminal-cloud/internal/embeddingprovider"
"data-terminal-cloud/internal/externalprobe"
"data-terminal-cloud/internal/extraction"
"data-terminal-cloud/internal/health"
"data-terminal-cloud/internal/httpapi"
"data-terminal-cloud/internal/idempotency"
"data-terminal-cloud/internal/identity"
"data-terminal-cloud/internal/media"
"data-terminal-cloud/internal/memoryindex"
"data-terminal-cloud/internal/memorylifecycle"
"data-terminal-cloud/internal/objectstore"
"data-terminal-cloud/internal/observability"
"data-terminal-cloud/internal/opsaccess"
"data-terminal-cloud/internal/opsconfig"
"data-terminal-cloud/internal/orchestration"
"data-terminal-cloud/internal/outbox"
"data-terminal-cloud/internal/pagination"
"data-terminal-cloud/internal/privacy"
"data-terminal-cloud/internal/ratelimit"
"data-terminal-cloud/internal/rtc"
"data-terminal-cloud/internal/saas"
"data-terminal-cloud/internal/sharing"
"data-terminal-cloud/internal/voiceprovider"
"data-terminal-cloud/internal/webevents"
"github.com/jackc/pgx/v5/pgxpool"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func Run(ctx context.Context, serviceName string) error {
cfg, err := config.Load(serviceName)
if err != nil {
return fmt.Errorf("load configuration: %w", err)
}
logger := newLogger(cfg)
traceProvider, err := observability.SetupTracing(ctx, serviceName, cfg.Environment, cfg.OTLPEndpoint)
if err != nil {
return err
}
defer func() {
shutdownContext, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := traceProvider.Shutdown(shutdownContext); err != nil {
logger.Error("trace provider shutdown failed", "error", err)
}
}()
databasePool, err := database.Open(ctx, cfg.DatabaseURL, serviceName)
if err != nil {
return err
}
defer databasePool.Close()
redisClient := cache.Open(cfg.RedisAddr, cfg.RedisPassword, cfg.RedisDB)
defer func() { _ = redisClient.Close() }()
var exportStore *objectstore.Store
var memoryEmbedding embeddingprovider.Provider
if serviceName == "api" || serviceName == "worker" {
memoryEmbedding, err = newMemoryEmbeddingProvider(cfg)
if err != nil {
return fmt.Errorf("create memory embedding provider: %w", err)
}
exportStore, err = objectstore.New(cfg.ObjectStoreEndpoint, cfg.ObjectStorePublic, cfg.ObjectStoreAccessKey, cfg.ObjectStoreSecretKey, cfg.ObjectStoreBucket, cfg.ObjectStoreRegion, cfg.ObjectStoreSecure)
if err != nil {
return fmt.Errorf("create private export object store: %w", err)
}
ensureContext, cancel := context.WithTimeout(ctx, cfg.DependencyTimeout)
err = exportStore.EnsureBucket(ensureContext)
cancel()
if err != nil {
return fmt.Errorf("ensure private export bucket: %w", err)
}
}
readinessChecks := map[string]health.CheckFunc{
"postgres": observability.InstrumentDependencyCheck(serviceName, "postgres", databasePool.Ping),
"redis": observability.InstrumentDependencyCheck(serviceName, "redis", func(checkContext context.Context) error {
return redisClient.Ping(checkContext).Err()
}),
}
if exportStore != nil {
readinessChecks["object_store"] = observability.InstrumentDependencyCheck(serviceName, "object_store", exportStore.Ready)
}
readiness := health.NewChecker(cfg.DependencyTimeout, readinessChecks)
externalProbeRunner, err := newExternalProbeRunner(cfg, logger, databasePool)
if err != nil {
return fmt.Errorf("create external dependency probes: %w", err)
}
tokenVerifier := identity.NewOIDCVerifier(ctx, cfg.AuthIssuerURL, cfg.AuthJWKSURL, cfg.AuthAudience)
identityRepository := identity.NewRepository(databasePool)
var deviceVerifier *deviceauth.Verifier
var deviceRepository *device.Repository
var deviceTelemetryService *device.TelemetryService
var conversationCreator *orchestration.Service
var conversationHeartbeater *orchestration.HeartbeatService
var conversationTranscriptSubmitter *orchestration.TranscriptBatchService
var providerEventProcessor *orchestration.ProviderEventProcessor
var saasService *saas.Service
var mediaService *media.Service
var webEventService *webevents.Service
var sharingService *sharing.Service
var opsConfigService *opsconfig.Service
var billingService *billing.Service
var alertService *alerts.Service
var diagnosticService *diagnostics.Service
var opsAccessService *opsaccess.Service
if serviceName == "api" {
webEventService = webevents.NewService(databasePool)
cursorCodec, cursorErr := pagination.NewCodec([]byte(cfg.CursorSigningSecret))
if cursorErr != nil {
return fmt.Errorf("create pagination cursor codec: %w", cursorErr)
}
saasService = saas.NewService(databasePool, audit.NewRepository(), redisClient, cursorCodec)
opsConfigService, err = opsconfig.NewService(databasePool, audit.NewRepository())
if err != nil {
return fmt.Errorf("create operations configuration service: %w", err)
}
billingService, err = billing.NewService(databasePool, audit.NewRepository())
if err != nil {
return fmt.Errorf("create usage billing service: %w", err)
}
alertService, err = alerts.NewService(databasePool, audit.NewRepository())
if err != nil {
return fmt.Errorf("create alert operations service: %w", err)
}
diagnosticService, err = diagnostics.NewService(databasePool, idempotency.NewExecutor(databasePool), audit.NewRepository())
if err != nil {
return fmt.Errorf("create diagnostic service: %w", err)
}
opsAccessService, err = opsaccess.NewService(databasePool, idempotency.NewExecutor(databasePool), audit.NewRepository(), exportStore, 5*time.Minute)
if err != nil {
return fmt.Errorf("create temporary sensitive access service: %w", err)
}
saasService.ConfigureExportSigning(exportStore, cfg.ExportDownloadTTL)
saasService.ConfigureFirmwareStore(exportStore, 5*time.Minute)
if exportStore != nil {
mediaService, err = media.NewService(databasePool, outbox.NewStore(databasePool), audit.NewRepository(), exportStore, 15*time.Minute)
if err != nil {
return fmt.Errorf("create media service: %w", err)
}
sharingService, err = sharing.NewService(databasePool, audit.NewRepository(), exportStore, []byte(cfg.ShareTokenSecret), 5*time.Minute)
if err != nil {
return fmt.Errorf("create sharing service: %w", err)
}
}
deviceIdentityRepository := deviceauth.NewRepository(databasePool)
var deviceSecretResolver deviceauth.SecretResolver
if cfg.DeviceSecretDirectory != "" {
deviceSecretResolver, err = deviceauth.NewDirectorySecretResolver(cfg.DeviceSecretDirectory)
if err != nil {
return fmt.Errorf("create per-device secret resolver: %w", err)
}
} else {
deviceSecretResolver = deviceauth.StaticSecretResolver{"local-dev://simulated-terminal-1": []byte(cfg.DeviceLocalSecret)}
}
deviceVerifier, err = deviceauth.NewVerifier(
deviceIdentityRepository,
deviceSecretResolver,
deviceauth.NewRedisReplayGuard(redisClient, "voice-platform"),
cfg.DeviceClockSkew,
)
if err != nil {
return fmt.Errorf("create device verifier: %w", err)
}
deviceRepository = device.NewRepository(databasePool)
deviceTelemetryService, err = device.NewTelemetryService(databasePool, idempotency.NewExecutor(databasePool))
if err != nil {
return fmt.Errorf("create device telemetry service: %w", err)
}
var tokenIssuer rtc.TokenIssuer
if cfg.RTCTokenIssuer == "volcengine" {
tokenIssuer, err = rtc.NewVolcengineTokenIssuer(cfg.RTCAppID, cfg.RTCAppKey, cfg.RTCTokenTTL)
} else {
tokenIssuer, err = rtc.NewLocalTokenIssuer([]byte(cfg.RTCTokenSecret))
}
if err != nil {
return fmt.Errorf("create RTC token issuer: %w", err)
}
leaseCache, leaseErr := orchestration.NewRedisLeaseCache(redisClient, "voice-platform")
if leaseErr != nil {
return fmt.Errorf("create conversation lease cache: %w", leaseErr)
}
conversationCreator, err = orchestration.NewService(
idempotency.NewExecutor(databasePool), orchestration.NewRepository(), audit.NewRepository(),
outbox.NewStore(databasePool), tokenIssuer, leaseCache,
orchestration.ServiceOptions{
RTCAppID: cfg.RTCAppID, VoiceProvider: cfg.VoiceProvider, EngineMode: "s2s_hybrid", AllocationTTL: cfg.AllocationTTL,
TokenTTL: cfg.RTCTokenTTL, HeartbeatInterval: cfg.HeartbeatInterval, RequirePublishedRouting: cfg.Environment == config.EnvironmentProd,
},
)
if err != nil {
return fmt.Errorf("create conversation orchestration service: %w", err)
}
if err = conversationCreator.ConfigureMemoryEmbedding(databasePool, memoryEmbedding); err != nil {
return fmt.Errorf("configure conversation memory embedding: %w", err)
}
credentialRenewalWindow := cfg.RTCTokenTTL / 3
minimumRenewalWindow := time.Duration(cfg.HeartbeatInterval*2) * time.Second
if credentialRenewalWindow < minimumRenewalWindow {
credentialRenewalWindow = minimumRenewalWindow
}
if credentialRenewalWindow >= cfg.RTCTokenTTL {
credentialRenewalWindow = cfg.RTCTokenTTL / 2
}
conversationHeartbeater, err = orchestration.NewHeartbeatService(
databasePool, orchestration.NewRepository(), leaseCache, tokenIssuer,
orchestration.HeartbeatServiceOptions{
RTCAppID: cfg.RTCAppID, LeaseTTL: time.Duration(cfg.HeartbeatInterval*5) * time.Second,
TokenTTL: cfg.RTCTokenTTL, CredentialRenewalWindow: credentialRenewalWindow,
},
)
if err != nil {
return fmt.Errorf("create conversation heartbeat service: %w", err)
}
conversationTranscriptSubmitter, err = orchestration.NewTranscriptBatchService(
databasePool, idempotency.NewExecutor(databasePool), audit.NewRepository(), outbox.NewStore(databasePool),
)
if err != nil {
return fmt.Errorf("create conversation transcript batch service: %w", err)
}
}
if serviceName == "callbacks" {
providerEventProcessor, err = orchestration.NewProviderEventProcessor(databasePool, time.Duration(cfg.HeartbeatInterval*5)*time.Second)
if err != nil {
return fmt.Errorf("create provider event processor: %w", err)
}
}
limiter, err := ratelimit.NewPolicies(redisClient, "voice-platform:"+serviceName, map[string]ratelimit.Policy{
"network": {RatePerSecond: cfg.RateLimitRPS, Burst: cfg.RateLimitBurst},
"user": {RatePerSecond: cfg.RateLimitUserRPS, Burst: cfg.RateLimitUserBurst},
"tenant": {RatePerSecond: cfg.RateLimitTenantRPS, Burst: cfg.RateLimitTenantBurst},
"device": {RatePerSecond: cfg.RateLimitDeviceRPS, Burst: cfg.RateLimitDeviceBurst},
"provider_start": {RatePerSecond: cfg.ProviderStartRPS, Burst: cfg.ProviderStartBurst},
})
if err != nil {
return fmt.Errorf("create rate limiter: %w", err)
}
if serviceName == "worker" {
webEventService = webevents.NewService(databasePool)
outbox.RegisterMetrics(databasePool)
observability.RegisterBusinessMetrics(databasePool)
outboxWorker := outbox.NewWorker(
outbox.NewStore(databasePool),
logger,
outbox.WorkerOptions{PollInterval: cfg.WorkerPollInterval},
)
outboxWorker.Register("platform.noop", func(context.Context, outbox.Event) error { return nil })
alertWebhookProcessor, processorErr := alertnotification.NewProcessor(cfg.AlertWebhookURL, cfg.AlertWebhookSecret, cfg.AlertWebhookTimeout)
if processorErr != nil {
return fmt.Errorf("create alert webhook processor: %w", processorErr)
}
outboxWorker.Register("alert.webhook_requested", alertWebhookProcessor.Handle)
outboxWorker.Register("alert.webhook_test_requested", alertWebhookProcessor.Handle)
outboxWorker.Register("conversation.room_issued", func(_ context.Context, event outbox.Event) error {
logger.Info("conversation room issued event observed", "event_id", event.ID, "conversation_id", event.AggregateID)
return nil
})
var providerClient voiceprovider.Client
if cfg.VoiceProvider == "volcengine" {
template, readErr := os.ReadFile(cfg.VolcVoiceChatTemplate)
if readErr != nil {
return fmt.Errorf("read Volcengine voice-chat template: %w", readErr)
}
providerClient, err = voiceprovider.NewVolcengineClient(voiceprovider.VolcengineOptions{
BaseURL: cfg.VolcRTCBaseURL, AccessKeyID: cfg.VolcAccessKeyID, SecretAccessKey: cfg.VolcSecretAccessKey,
SessionToken: cfg.VolcSessionToken, Region: cfg.VolcRTCRegion, AppID: cfg.RTCAppID,
APIVersion: cfg.VolcRTCAPIVersion, Template: template, Timeout: cfg.ProviderRequestTimeout,
RequireAllEngineModes: cfg.Environment == config.EnvironmentProd,
})
} else {
providerClient, err = voiceprovider.NewHTTPClient(cfg.MockProviderBaseURL, cfg.MockProviderAPIKey, cfg.ProviderRequestTimeout)
}
if err != nil {
return fmt.Errorf("create voice provider client: %w", err)
}
providerHealth, providerErr := orchestration.NewProviderHealth(databasePool, cfg.ProviderFailureThreshold, cfg.ProviderCircuitCooldown)
if providerErr != nil {
return fmt.Errorf("create voice provider health: %w", providerErr)
}
leaseTTL := time.Duration(cfg.HeartbeatInterval*5) * time.Second
startProcessor, processorErr := orchestration.NewStartProcessor(databasePool, providerClient, providerHealth, leaseTTL)
if processorErr != nil {
return fmt.Errorf("create provider start processor: %w", processorErr)
}
if processorErr = startProcessor.ConfigureStartProtection(limiter, cfg.ProviderMaxActiveTasks); processorErr != nil {
return fmt.Errorf("configure provider start protection: %w", processorErr)
}
outboxWorker.Register("conversation.agent_start_requested", startProcessor.Handle)
outboxWorker.Register("conversation.agent_start_reconcile_requested", startProcessor.Handle)
stopProcessor, processorErr := orchestration.NewStopProcessor(databasePool, providerClient, providerHealth)
if processorErr != nil {
return fmt.Errorf("create provider stop processor: %w", processorErr)
}
outboxWorker.Register("conversation.agent_stop_requested", stopProcessor.Handle)
outboxWorker.Register("conversation.agent_stop_reconcile_requested", stopProcessor.Handle)
var contentGenerator extraction.Generator = extraction.DeterministicGenerator{}
if cfg.ContentGenerator == "ark" {
arkGenerator, generatorErr := extraction.NewArkGenerator(cfg.ArkBaseURL, cfg.ArkAPIKey, cfg.ArkContentModel, cfg.ArkRequestTimeout)
if generatorErr != nil {
return fmt.Errorf("create Ark content generator: %w", generatorErr)
}
contentGenerator = arkGenerator
}
var contentSafety contentpolicy.Classifier = contentpolicy.LocalClassifier{}
if cfg.ContentSafetyProvider == "managed_http" {
managedSafety, safetyErr := contentpolicy.NewManagedClassifier(cfg.ContentSafetyEndpoint, cfg.ContentSafetyAPIKey, cfg.ContentSafetyPolicyRef, cfg.ContentSafetyTimeout)
if safetyErr != nil {
return fmt.Errorf("create managed content safety classifier: %w", safetyErr)
}
contentSafety = managedSafety
}
extractionProcessor, processorErr := extraction.NewProcessorWithSafety(databasePool, contentGenerator, contentSafety)
if processorErr != nil {
return fmt.Errorf("create content extraction processor: %w", processorErr)
}
outboxWorker.Register("conversation.content_extraction_requested", extractionProcessor.Handle)
memoryIndexProcessor, processorErr := memoryindex.NewProcessor(databasePool, memoryEmbedding)
if processorErr != nil {
return fmt.Errorf("create memory index processor: %w", processorErr)
}
outboxWorker.Register("memory.index_requested", memoryIndexProcessor.Handle)
memoryDeleteProcessor, processorErr := memorylifecycle.NewDeleteProcessor(databasePool)
if processorErr != nil {
return fmt.Errorf("create memory delete processor: %w", processorErr)
}
outboxWorker.Register("memory.delete_requested", memoryDeleteProcessor.Handle)
privacyAccessProcessor, processorErr := privacy.NewAccessProcessor(databasePool)
if processorErr != nil {
return fmt.Errorf("create privacy access processor: %w", processorErr)
}
outboxWorker.Register("privacy.access_requested", privacyAccessProcessor.Handle)
privacyDeleteProcessor, processorErr := privacy.NewDeleteProcessor(databasePool, redisClient, exportStore)
if processorErr != nil {
return fmt.Errorf("create privacy delete processor: %w", processorErr)
}
outboxWorker.Register("privacy.delete_requested", privacyDeleteProcessor.Handle)
exportProcessor, processorErr := dataexport.NewProcessor(databasePool, exportStore, cfg.ExportObjectTTL)
if processorErr != nil {
return fmt.Errorf("create data export processor: %w", processorErr)
}
outboxWorker.Register("export.generate_requested", exportProcessor.Handle)
if exportStore != nil {
mediaProcessor, mediaErr := media.NewProcessor(databasePool, exportStore, audit.NewRepository(), cfg.MediaLocalDeleteGrace)
if mediaErr != nil {
return fmt.Errorf("create media verification processor: %w", mediaErr)
}
outboxWorker.Register("media.verify_requested", mediaProcessor.Handle)
}
expiryScanner, processorErr := dataexport.NewExpiryScanner(databasePool, exportStore, logger, cfg.WorkerPollInterval)
if processorErr != nil {
return fmt.Errorf("create export expiry scanner: %w", processorErr)
}
go expiryScanner.Run(ctx)
go outboxWorker.Run(ctx)
recoveryScanner, recoveryErr := orchestration.NewRecoveryScanner(
databasePool, outbox.NewStore(databasePool), logger,
orchestration.RecoveryOptions{
ScanInterval: cfg.RecoveryScanInterval, StartingAfter: cfg.RecoveryStartingAfter,
StoppingAfter: cfg.RecoveryStoppingAfter, BatchSize: 50,
},
)
if recoveryErr != nil {
return fmt.Errorf("create conversation recovery scanner: %w", recoveryErr)
}
go recoveryScanner.Run(ctx)
alertScanner, alertErr := alerts.NewScanner(databasePool, logger, cfg.WorkerPollInterval)
if alertErr != nil {
return fmt.Errorf("create alert scanner: %w", alertErr)
}
go alertScanner.Run(ctx)
go func() {
ticker := time.NewTicker(time.Duration(cfg.HeartbeatInterval) * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
staleBefore := time.Now().UTC().Add(-2 * leaseTTL)
if _, err := webEventService.ExpireDevicePresence(ctx, staleBefore, 1000); err != nil {
logger.Warn("device presence expiry scan failed", "error", err)
}
if _, err := webEventService.DeleteExpired(ctx, 5000); err != nil {
logger.Warn("expired web event cleanup failed", "error", err)
}
}
}
}()
}
router := httpapi.NewRouter(httpapi.RouterOptions{
ServiceName: serviceName,
Logger: logger,
RateLimiter: rateLimiterAdapter{limiter},
Readiness: readiness,
TokenVerifier: tokenVerifier,
IdentityRepository: identityRepository,
SaaSService: saasService,
DeviceVerifier: deviceVerifier,
DeviceService: deviceRepository,
DeviceTelemetrySubmitter: deviceTelemetryService,
MediaService: mediaService,
WebEventService: webEventService,
SharingService: sharingService,
OpsConfigService: opsConfigService,
BillingService: billingService,
AlertService: alertService,
DiagnosticService: diagnosticService,
OpsAccessService: opsAccessService,
ConversationCreator: conversationCreator,
ConversationActivator: conversationCreator,
DeviceConversationStateReader: conversationCreator,
ConversationHeartbeater: conversationHeartbeater,
ConversationStopper: conversationCreator,
ConversationTranscriptSubmitter: conversationTranscriptSubmitter,
ProviderCallbackSecret: []byte(cfg.ProviderCallbackSecret),
ProviderCallbackSkew: cfg.ProviderCallbackSkew,
ProviderCallbackProcessor: providerEventProcessor,
})
server := &http.Server{
Addr: cfg.HTTPAddr,
Handler: otelhttp.NewHandler(httpapi.Middleware(logger, serviceName, 1<<20, rateLimiterAdapter{limiter}, router), serviceName),
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 15 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
}
serverError := make(chan error, 1)
go externalProbeRunner.Run(ctx)
go func() {
logger.Info("service listening", "address", cfg.HTTPAddr, "environment", cfg.Environment)
serverError <- server.ListenAndServe()
}()
select {
case <-ctx.Done():
shutdownContext, cancel := context.WithTimeout(context.Background(), cfg.ShutdownTimeout)
defer cancel()
logger.Info("service shutting down")
return server.Shutdown(shutdownContext)
case err := <-serverError:
if err == http.ErrServerClosed {
return nil
}
return err
}
}
func newExternalProbeRunner(cfg config.Config, logger *slog.Logger, pool *pgxpool.Pool) (*externalprobe.Runner, error) {
checks, err := externalProbeChecks(cfg)
if err != nil {
return nil, err
}
dependencies := make([]string, 0, len(checks))
for dependency := range checks {
dependencies = append(dependencies, dependency)
}
reconcileContext, cancel := context.WithTimeout(context.Background(), cfg.DependencyTimeout)
err = externalprobe.ReconcilePostgresState(reconcileContext, pool, cfg.ServiceName, dependencies)
cancel()
if err != nil {
return nil, err
}
observer, err := externalprobe.PostgresObserver(pool, cfg.ServiceName)
if err != nil {
return nil, err
}
return externalprobe.NewRunner(cfg.ExternalProbeInterval, cfg.DependencyTimeout, checks, logger, observer)
}
func externalProbeChecks(cfg config.Config) (map[string]externalprobe.CheckFunc, error) {
checks := map[string]externalprobe.CheckFunc{}
if cfg.ServiceName == "api" {
jwksCheck, err := externalprobe.JWKS(cfg.AuthJWKSURL, nil)
if err != nil {
return nil, err
}
checks["oidc_jwks"] = observability.InstrumentDependencyCheck(cfg.ServiceName, "oidc_jwks", jwksCheck)
}
if (cfg.ServiceName == "api" || cfg.ServiceName == "worker") && cfg.EmbeddingProvider == "ark" {
arkCheck, err := externalprobe.TLSReachability(cfg.ArkBaseURL, nil)
if err != nil {
return nil, err
}
checks["ark_tls"] = observability.InstrumentDependencyCheck(cfg.ServiceName, "ark_tls", arkCheck)
}
if cfg.ServiceName == "worker" && cfg.VoiceProvider == "volcengine" {
rtcCheck, err := externalprobe.TLSReachability(cfg.VolcRTCBaseURL, nil)
if err != nil {
return nil, err
}
checks["volc_rtc_tls"] = observability.InstrumentDependencyCheck(cfg.ServiceName, "volc_rtc_tls", rtcCheck)
}
if cfg.ServiceName == "worker" && cfg.ContentSafetyProvider == "managed_http" {
contentSafetyCheck, err := externalprobe.TLSReachability(cfg.ContentSafetyEndpoint, nil)
if err != nil {
return nil, err
}
checks["content_safety_tls"] = observability.InstrumentDependencyCheck(cfg.ServiceName, "content_safety_tls", contentSafetyCheck)
}
return checks, nil
}
func newMemoryEmbeddingProvider(cfg config.Config) (embeddingprovider.Provider, error) {
if cfg.EmbeddingProvider == "ark" {
return embeddingprovider.NewArk(cfg.ArkBaseURL, cfg.ArkAPIKey, cfg.ArkEmbeddingModel, cfg.ArkRequestTimeout)
}
return embeddingprovider.Local{}, nil
}
type rateLimiterAdapter struct{ limiter *ratelimit.Limiter }
func (a rateLimiterAdapter) Allow(ctx context.Context, scope, identifier string) (httpapi.RateLimitResult, error) {
result, err := a.limiter.Allow(ctx, scope, identifier)
return httpapi.RateLimitResult{Allowed: result.Allowed, Remaining: result.Remaining, RetryAfter: result.RetryAfter}, err
}
func newLogger(cfg config.Config) *slog.Logger {
level := slog.LevelInfo
switch cfg.LogLevel {
case "debug":
level = slog.LevelDebug
case "warn":
level = slog.LevelWarn
case "error":
level = slog.LevelError
}
return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level})).With("service", cfg.ServiceName)
}
+71
View File
@@ -0,0 +1,71 @@
package app
import (
"context"
"strings"
"testing"
"time"
"data-terminal-cloud/internal/cache"
"data-terminal-cloud/internal/config"
"data-terminal-cloud/internal/ratelimit"
)
func TestCallbacksApplicationStartsAndShutsDown(t *testing.T) {
t.Setenv("APP_ENV", "test")
t.Setenv("HTTP_ADDR", "127.0.0.1:0")
t.Setenv("DATABASE_URL", "postgres://voice:voice_dev_only@localhost:55432/voice_platform?sslmode=disable")
t.Setenv("REDIS_ADDR", "localhost:56379")
t.Setenv("SHUTDOWN_TIMEOUT", "1s")
t.Setenv("DEPENDENCY_TIMEOUT", "1s")
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond)
defer cancel()
if err := Run(ctx, "callbacks"); err != nil {
t.Fatalf("callbacks lifecycle: %v", err)
}
}
func TestApplicationRejectsInvalidConfiguration(t *testing.T) {
t.Setenv("REDIS_DB", "not-an-integer")
if err := Run(context.Background(), "callbacks"); err == nil || !strings.Contains(err.Error(), "load configuration") {
t.Fatalf("invalid configuration error=%v", err)
}
}
func TestLoggerLevelsAndRateLimiterAdapter(t *testing.T) {
for _, level := range []string{"debug", "info", "warn", "error"} {
logger := newLogger(config.Config{ServiceName: "app-integration", LogLevel: level})
if logger == nil {
t.Fatalf("nil logger for level %s", level)
}
}
redisClient := cache.Open("localhost:56379", "", 0)
defer redisClient.Close()
limiter, err := ratelimit.NewPolicies(redisClient, "app-integration", map[string]ratelimit.Policy{"network": {RatePerSecond: 100, Burst: 2}})
if err != nil {
t.Fatal(err)
}
result, err := (rateLimiterAdapter{limiter: limiter}).Allow(context.Background(), "network", "synthetic-client")
if err != nil || !result.Allowed || result.Remaining < 0 {
t.Fatalf("rate limit result=%+v err=%v", result, err)
}
}
func TestExternalProbeChecksIncludeManagedContentSafetyWithoutCallingIt(t *testing.T) {
cfg := config.Config{
ServiceName: "worker", ContentSafetyProvider: "managed_http",
ContentSafetyEndpoint: "https://content-safety.example.com/v1/classify",
}
checks, err := externalProbeChecks(cfg)
if err != nil {
t.Fatal(err)
}
if len(checks) != 1 || checks["content_safety_tls"] == nil {
t.Fatalf("unexpected external probes: %+v", checks)
}
cfg.ContentSafetyProvider = "local"
checks, err = externalProbeChecks(cfg)
if err != nil || len(checks) != 0 {
t.Fatalf("local content safety must not create an external probe: checks=%+v err=%v", checks, err)
}
}