6bf8bebf51
CI / Test and Build (push) Failing after 1s
CI / Migrate Dev DB (push) Has been skipped
CI / Migrate DB (push) Has been skipped
CodeQL / Analyze actions (push) Has been cancelled
CodeQL / Analyze javascript-typescript (push) Has been cancelled
CI / Detect Version (push) Has been cancelled
CI / Detect Desktop Changes (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/cron.Dockerfile, ubuntu-latest, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build AMD64 (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/cron.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/db.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/pii.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/realtime.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-8vcpu-ubuntu-2404-arm, ./docker/app.Dockerfile, linux-arm64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
Helm Chart / Lint, test, and validate chart (push) Has been cancelled
Helm Chart / Chart version bumped (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Build Dev ECR (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core) (push) Has been cancelled
CI / Promote Images (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Check Desktop Signing Secrets (push) Has been cancelled
CI / Desktop Release (push) Has been cancelled
CI / Create Desktop Prerelease (push) Has been cancelled
CI / Desktop Prerelease Build (push) Has been cancelled
CI / Publish Desktop Prerelease (push) Has been cancelled
CI / Prune Desktop Prereleases (push) Has been cancelled
Helm Chart / Install on kind and run helm test (push) Has been cancelled
274 lines
9.6 KiB
TypeScript
274 lines
9.6 KiB
TypeScript
import { describe, expect, it } from 'bun:test'
|
|
import { OAUTH_CLIENT_CAPABILITIES } from '../../apps/sim/lib/core/config/env-capabilities.ts'
|
|
import { buildEnvCapabilityStatus } from './capability-status.ts'
|
|
|
|
const DAYTONA_FUNCTION_SNAPSHOT_ID = '00000000-0000-4000-8000-000000000002'
|
|
|
|
describe('env capability status', () => {
|
|
it('reports built-in defaults without treating them as configured services', () => {
|
|
const status = buildEnvCapabilityStatus({})
|
|
|
|
expect(status.features.email).toMatchObject({ state: 'missing', providerIds: [] })
|
|
expect(status.features.storage).toMatchObject({ state: 'default', providerId: 'local' })
|
|
expect(status.features.sandbox).toMatchObject({ state: 'default', providerId: 'disabled' })
|
|
expect(status.features.jobs).toMatchObject({ state: 'default', providerId: 'database' })
|
|
expect(status.features.cache).toMatchObject({ state: 'default', providerId: 'database' })
|
|
expect(status.features.knowledge).toMatchObject({ state: 'default', providerId: 'local' })
|
|
expect(status.features.llm).toMatchObject({
|
|
state: 'missing',
|
|
configuredPoolCount: 0,
|
|
configuredKeyCount: 0,
|
|
effectiveKeyCount: 0,
|
|
})
|
|
expect(status.oauthClients.absentCount).toBe(Object.keys(OAUTH_CLIENT_CAPABILITIES).length)
|
|
})
|
|
|
|
it('reports an explicitly selected but disabled E2B provider as the default', () => {
|
|
const status = buildEnvCapabilityStatus({
|
|
SANDBOX_PROVIDER: 'e2b',
|
|
E2B_ENABLED: 'false',
|
|
NEXT_PUBLIC_E2B_ENABLED: 'false',
|
|
NEXT_PUBLIC_SANDBOXES_ENABLED: 'false',
|
|
})
|
|
|
|
expect(status.features.sandbox).toEqual({
|
|
id: 'sandbox',
|
|
label: 'Function sandboxes',
|
|
setupCommand: 'bun run setup sandbox',
|
|
state: 'default',
|
|
providerId: 'disabled',
|
|
})
|
|
})
|
|
|
|
it('preserves declared email fallback order', () => {
|
|
const status = buildEnvCapabilityStatus({
|
|
SMTP_HOST: 'localhost',
|
|
SMTP_PORT: '1025',
|
|
RESEND_API_KEY: 'secret-resend-key',
|
|
GMAIL_CREDENTIALS_JSON: JSON.stringify({
|
|
client_email: 'mailer@example.com',
|
|
private_key: 'secret-private-key',
|
|
}),
|
|
GMAIL_SENDER: 'mailer@example.com',
|
|
})
|
|
|
|
expect(status.features.email).toMatchObject({
|
|
state: 'configured',
|
|
providerIds: ['resend', 'smtp', 'gmail'],
|
|
})
|
|
expect(JSON.stringify(status)).not.toContain('secret-resend-key')
|
|
expect(JSON.stringify(status)).not.toContain('secret-private-key')
|
|
})
|
|
|
|
it('reports selected Daytona and cloud storage providers', () => {
|
|
const status = buildEnvCapabilityStatus({
|
|
SANDBOX_PROVIDER: 'daytona',
|
|
DAYTONA_API_KEY: 'daytona-secret',
|
|
DAYTONA_FUNCTION_SNAPSHOT_ID,
|
|
NEXT_PUBLIC_SANDBOXES_ENABLED: 'true',
|
|
STORAGE_PROVIDER: 's3',
|
|
AWS_REGION: 'us-east-1',
|
|
S3_BUCKET_NAME: 'files',
|
|
})
|
|
|
|
expect(status.features.sandbox).toMatchObject({
|
|
state: 'configured',
|
|
providerId: 'daytona',
|
|
})
|
|
expect(status.features.storage).toMatchObject({
|
|
state: 'configured',
|
|
providerId: 's3',
|
|
})
|
|
})
|
|
|
|
it('reports Daytona as missing when its Function snapshot is absent', () => {
|
|
const status = buildEnvCapabilityStatus({
|
|
SANDBOX_PROVIDER: 'daytona',
|
|
DAYTONA_API_KEY: 'daytona-secret',
|
|
NEXT_PUBLIC_SANDBOXES_ENABLED: 'true',
|
|
})
|
|
|
|
expect(status.features.sandbox).toMatchObject({
|
|
state: 'missing',
|
|
providerId: 'daytona',
|
|
issue: { state: 'missing' },
|
|
})
|
|
expect(status.features.sandbox.issue?.message).toContain('DAYTONA_FUNCTION_SNAPSHOT_ID')
|
|
})
|
|
|
|
it('reports a mutable Daytona Function snapshot name as invalid', () => {
|
|
const status = buildEnvCapabilityStatus({
|
|
SANDBOX_PROVIDER: 'daytona',
|
|
DAYTONA_API_KEY: 'daytona-secret',
|
|
DAYTONA_FUNCTION_SNAPSHOT_ID: 'mothership-shell:latest',
|
|
NEXT_PUBLIC_SANDBOXES_ENABLED: 'true',
|
|
})
|
|
|
|
expect(status.features.sandbox).toMatchObject({
|
|
state: 'invalid',
|
|
providerId: 'daytona',
|
|
issue: { state: 'invalid' },
|
|
})
|
|
expect(status.features.sandbox.issue?.message).toContain('immutable Daytona snapshot ID')
|
|
})
|
|
|
|
it('reports remote sandbox server/browser drift as partial', () => {
|
|
const status = buildEnvCapabilityStatus({
|
|
SANDBOX_PROVIDER: 'daytona',
|
|
DAYTONA_API_KEY: 'daytona-secret',
|
|
DAYTONA_FUNCTION_SNAPSHOT_ID,
|
|
})
|
|
|
|
expect(status.features.sandbox).toMatchObject({
|
|
state: 'partial',
|
|
providerId: 'daytona',
|
|
issue: { state: 'partial' },
|
|
})
|
|
expect(status.features.sandbox.issue?.message).toContain('NEXT_PUBLIC_SANDBOXES_ENABLED')
|
|
})
|
|
|
|
it('captures partial and invalid entries without aborting the snapshot', () => {
|
|
const status = buildEnvCapabilityStatus({
|
|
SMTP_HOST: 'localhost',
|
|
TRIGGER_DEV_ENABLED: 'true',
|
|
TRIGGER_PROJECT_ID: 'project',
|
|
REDIS_URL: 'not-a-url',
|
|
OCR_AZURE_ENDPOINT: 'https://ocr.example.com',
|
|
})
|
|
|
|
expect(status.features.email).toMatchObject({ state: 'partial' })
|
|
expect(
|
|
status.features.email.providers.find((provider) => provider.id === 'smtp')
|
|
).toMatchObject({
|
|
state: 'partial',
|
|
missingFields: ['SMTP_PORT'],
|
|
})
|
|
expect(status.features.jobs).toMatchObject({ state: 'partial', providerId: 'trigger-dev' })
|
|
expect(status.features.cache).toMatchObject({ state: 'invalid', providerId: 'redis' })
|
|
expect(status.features.knowledge).toMatchObject({
|
|
state: 'partial',
|
|
providerId: 'azure-mistral',
|
|
})
|
|
expect(status.features.storage).toMatchObject({ state: 'default', providerId: 'local' })
|
|
})
|
|
|
|
it('does not expose invalid selector values', () => {
|
|
const sensitiveValue = 'sensitive-selector-value'
|
|
const snapshots = [
|
|
buildEnvCapabilityStatus({ STORAGE_PROVIDER: sensitiveValue }),
|
|
buildEnvCapabilityStatus({ SANDBOX_PROVIDER: sensitiveValue }),
|
|
buildEnvCapabilityStatus({ OCR_PROVIDER: sensitiveValue }),
|
|
]
|
|
|
|
for (const snapshot of snapshots) {
|
|
expect(JSON.stringify(snapshot)).not.toContain(sensitiveValue)
|
|
}
|
|
})
|
|
|
|
it('reports every OAuth client group as ready, partial, or absent', () => {
|
|
const status = buildEnvCapabilityStatus({
|
|
GOOGLE_CLIENT_ID: 'google-id',
|
|
GOOGLE_CLIENT_SECRET: 'google-secret',
|
|
MICROSOFT_CLIENT_ID: 'microsoft-id',
|
|
})
|
|
|
|
expect(status.oauthClients.clients.google).toMatchObject({
|
|
state: 'ready',
|
|
configuredFieldCount: 2,
|
|
requiredFieldCount: 2,
|
|
})
|
|
expect(status.oauthClients.clients.microsoft).toMatchObject({
|
|
state: 'partial',
|
|
configuredFieldCount: 1,
|
|
missingFields: ['MICROSOFT_CLIENT_SECRET'],
|
|
})
|
|
expect(status.oauthClients.clients.slack).toMatchObject({
|
|
state: 'absent',
|
|
configuredFieldCount: 0,
|
|
})
|
|
expect(status.oauthClients.readyCount).toBe(1)
|
|
expect(status.oauthClients.partialCount).toBe(1)
|
|
expect(status.oauthClients.absentCount).toBe(Object.keys(OAUTH_CLIENT_CAPABILITIES).length - 2)
|
|
})
|
|
|
|
it('counts configured and effective LLM pool keys without exposing them', () => {
|
|
const status = buildEnvCapabilityStatus({
|
|
OPENAI_API_KEY_1: 'openai-one',
|
|
OPENAI_API_KEY_3: 'openai-three',
|
|
FIREWORKS_API_KEY: 'fireworks-fallback',
|
|
FIREWORKS_API_KEY_1: 'fireworks-one',
|
|
})
|
|
|
|
expect(status.features.llm).toMatchObject({
|
|
state: 'configured',
|
|
configuredPoolCount: 2,
|
|
configuredKeyCount: 4,
|
|
effectiveKeyCount: 3,
|
|
})
|
|
expect(status.features.llm.pools.openai).toMatchObject({
|
|
state: 'configured',
|
|
configuredKeyCount: 2,
|
|
effectiveKeyCount: 2,
|
|
fallbackKeyConfigured: false,
|
|
})
|
|
expect(status.features.llm.pools.fireworks).toMatchObject({
|
|
state: 'configured',
|
|
configuredKeyCount: 2,
|
|
effectiveKeyCount: 1,
|
|
fallbackKeyConfigured: true,
|
|
})
|
|
expect(JSON.stringify(status)).not.toContain('openai-one')
|
|
expect(JSON.stringify(status)).not.toContain('fireworks-fallback')
|
|
})
|
|
|
|
it('counts singular runtime LLM keys as effective pool fallbacks', () => {
|
|
const status = buildEnvCapabilityStatus({
|
|
OPENAI_API_KEY: 'openai-fallback',
|
|
GEMINI_API_KEY: 'gemini-fallback',
|
|
COHERE_API_KEY: 'cohere-fallback',
|
|
})
|
|
|
|
expect(status.features.llm).toMatchObject({
|
|
state: 'configured',
|
|
configuredPoolCount: 3,
|
|
configuredKeyCount: 3,
|
|
effectiveKeyCount: 3,
|
|
})
|
|
expect(status.features.llm.pools.openai).toMatchObject({
|
|
state: 'configured',
|
|
fallbackKeyConfigured: true,
|
|
})
|
|
expect(status.features.llm.pools.gemini).toMatchObject({
|
|
state: 'configured',
|
|
fallbackKeyConfigured: true,
|
|
})
|
|
expect(status.features.llm.pools.cohere).toMatchObject({
|
|
state: 'configured',
|
|
fallbackKeyConfigured: true,
|
|
})
|
|
expect(JSON.stringify(status)).not.toContain('openai-fallback')
|
|
expect(JSON.stringify(status)).not.toContain('gemini-fallback')
|
|
expect(JSON.stringify(status)).not.toContain('cohere-fallback')
|
|
})
|
|
|
|
it('reports non-Redis cache URLs and non-HTTP Azure OCR endpoints as invalid', () => {
|
|
const status = buildEnvCapabilityStatus({
|
|
REDIS_URL: 'https://cache.example.com',
|
|
OCR_PROVIDER: 'azure-mistral',
|
|
OCR_AZURE_API_KEY: 'azure-key',
|
|
OCR_AZURE_ENDPOINT: 'ftp://ocr.example.com',
|
|
OCR_AZURE_MODEL_NAME: 'mistral-ocr',
|
|
})
|
|
|
|
expect(status.features.cache).toMatchObject({ state: 'invalid', providerId: 'redis' })
|
|
expect(status.features.cache.issue?.message).toContain('redis:// or rediss://')
|
|
expect(status.features.knowledge).toMatchObject({
|
|
state: 'invalid',
|
|
providerId: 'azure-mistral',
|
|
})
|
|
expect(status.features.knowledge.issue?.message).toContain(
|
|
'OCR_AZURE_ENDPOINT must be a valid HTTP(S) URL'
|
|
)
|
|
})
|
|
})
|