239 lines
9.5 KiB
JavaScript
239 lines
9.5 KiB
JavaScript
import fs from 'node:fs'
|
|
import os from 'node:os'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
buildBundleValidationReport,
|
|
buildFrontendEnvEntries,
|
|
buildServerEnvEntries,
|
|
loadBundleManifest,
|
|
renderSameOriginBundle,
|
|
writeSameOriginBundle,
|
|
} from './render-same-origin-bundle-lib.mjs'
|
|
|
|
const currentFile = fileURLToPath(import.meta.url)
|
|
const websiteRoot = path.resolve(path.dirname(currentFile), '..')
|
|
const launchBundleExamplePath = path.join(websiteRoot, 'deploy', 'hypertwist.same-origin.bundle.example.json')
|
|
const previewBundleExamplePath = path.join(websiteRoot, 'deploy', 'hypertwist.same-origin.preview.bundle.example.json')
|
|
|
|
function createValidManifest() {
|
|
return {
|
|
deploymentTier: 'launch',
|
|
checkoutRoot: '/srv/hypertwist/current',
|
|
publicOrigin: 'https://hypertwist.app',
|
|
supportEmail: 'hello@hypertwist.app',
|
|
publicDocsUrl: 'https://docs.hypertwist.app',
|
|
releaseNotesUrl: 'https://notes.hypertwist.app',
|
|
correspondingSourceUrl: 'https://hypertwist.app/open-source/source.zip',
|
|
openSourceRepoUrl: 'https://git.scriptoriumai.io/scriptoriumadmin/hypertwist',
|
|
operatorCheckoutUrl: 'https://buy.paddle.com/operator',
|
|
studioCheckoutUrl: 'https://buy.paddle.com/studio',
|
|
windowsDownloadUrl: 'https://downloads.hypertwist.app/windows.exe',
|
|
releaseManifestVersion: '1.0.0',
|
|
releaseManifestChannel: 'candidate',
|
|
windowsRelease: {
|
|
buildId: 'win64-1000',
|
|
sha256: 'abc123',
|
|
fileSizeBytes: 123456789,
|
|
publishedAt: '2026-06-22T00:00:00.000Z',
|
|
},
|
|
server: {
|
|
paddleWebhookSecret: 'secret',
|
|
paddleProductPlanMap: {
|
|
prod_operator: 'operator',
|
|
},
|
|
paddlePricePlanMap: {
|
|
pri_operator: 'operator',
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
function createPreviewManifest() {
|
|
return {
|
|
deploymentTier: 'preview',
|
|
checkoutRoot: '/srv/hypertwist/current',
|
|
publicOrigin: 'https://hypertwist.app',
|
|
supportEmail: 'hello@hypertwist.app',
|
|
publicDocsUrl: 'https://hypertwist.app/docs',
|
|
releaseNotesUrl: 'https://hypertwist.app/changelog',
|
|
correspondingSourceUrl: 'https://git.scriptoriumai.io/scriptoriumadmin/hypertwist',
|
|
openSourceRepoUrl: 'https://git.scriptoriumai.io/scriptoriumadmin/hypertwist',
|
|
operatorCheckoutUrl: '',
|
|
studioCheckoutUrl: '',
|
|
windowsDownloadUrl: '',
|
|
releaseManifestVersion: '',
|
|
releaseManifestChannel: 'preview',
|
|
windowsRelease: {
|
|
buildId: '',
|
|
sha256: '',
|
|
fileSizeBytes: '',
|
|
publishedAt: '',
|
|
},
|
|
server: {
|
|
paddleWebhookSecret: '',
|
|
paddleProductPlanMap: {},
|
|
paddlePricePlanMap: {},
|
|
},
|
|
}
|
|
}
|
|
|
|
describe('buildFrontendEnvEntries', () => {
|
|
it('derives consistent frontend env from the bundle manifest', () => {
|
|
const env = buildFrontendEnvEntries(createValidManifest())
|
|
|
|
expect(env.VITE_AUTH_API_BASE_URL).toBe('https://hypertwist.app')
|
|
expect(env.VITE_WINDOWS_DOWNLOAD_URL).toBe('https://downloads.hypertwist.app/windows.exe')
|
|
expect(env.VITE_PADDLE_CHECKOUT_URL_OPERATOR).toBe('https://buy.paddle.com/operator')
|
|
})
|
|
|
|
it('uses the manifest public origin when deriving auth env', () => {
|
|
const env = buildFrontendEnvEntries({
|
|
...createValidManifest(),
|
|
publicOrigin: 'https://preview.hypertwist.app',
|
|
})
|
|
|
|
expect(env.VITE_SUPERTOKENS_API_DOMAIN).toBe('https://preview.hypertwist.app')
|
|
expect(env.VITE_SUPERTOKENS_WEBSITE_DOMAIN).toBe('https://preview.hypertwist.app')
|
|
expect(env.VITE_AUTH_API_BASE_URL).toBe('https://preview.hypertwist.app')
|
|
})
|
|
|
|
it('enables ORCID in frontend env only when the server manifest carries a complete ORCID credential pair', () => {
|
|
const env = buildFrontendEnvEntries({
|
|
...createValidManifest(),
|
|
server: {
|
|
...createValidManifest().server,
|
|
orcidClientId: 'orcid-client-id',
|
|
orcidClientSecret: 'orcid-client-secret',
|
|
},
|
|
})
|
|
|
|
expect(env.VITE_ORCID_OAUTH_ENABLED).toBe('true')
|
|
})
|
|
})
|
|
|
|
describe('buildServerEnvEntries', () => {
|
|
it('derives consistent server env from the bundle manifest', () => {
|
|
const env = buildServerEnvEntries(createValidManifest())
|
|
|
|
expect(env.PORT).toBe('3011')
|
|
expect(env.API_DOMAIN).toBe('https://hypertwist.app')
|
|
expect(env.WINDOWS_RELEASE_BUILD_ID).toBe('win64-1000')
|
|
expect(env.PADDLE_PRODUCT_PLAN_MAP).toContain('prod_operator')
|
|
})
|
|
|
|
it('derives the public origin from server names when no explicit publicOrigin is set', () => {
|
|
const env = buildServerEnvEntries({
|
|
...createValidManifest(),
|
|
publicOrigin: '',
|
|
serverNames: ['preview.hypertwist.app', 'hypertwist.app'],
|
|
})
|
|
|
|
expect(env.API_DOMAIN).toBe('https://preview.hypertwist.app')
|
|
expect(env.WEBSITE_DOMAIN).toBe('https://preview.hypertwist.app')
|
|
})
|
|
|
|
it('carries ORCID server env through when the manifest declares the custom provider credentials', () => {
|
|
const env = buildServerEnvEntries({
|
|
...createValidManifest(),
|
|
server: {
|
|
...createValidManifest().server,
|
|
orcidClientId: 'orcid-client-id',
|
|
orcidClientSecret: 'orcid-client-secret',
|
|
orcidIsSandbox: true,
|
|
},
|
|
})
|
|
|
|
expect(env.ORCID_CLIENT_ID).toBe('orcid-client-id')
|
|
expect(env.ORCID_CLIENT_SECRET).toBe('orcid-client-secret')
|
|
expect(env.ORCID_IS_SANDBOX).toBe('true')
|
|
})
|
|
})
|
|
|
|
describe('buildBundleValidationReport', () => {
|
|
it('fails when placeholder values are still present in the manifest', () => {
|
|
const report = buildBundleValidationReport({
|
|
...createValidManifest(),
|
|
operatorCheckoutUrl: 'https://buy.paddle.com/replace-me-operator',
|
|
})
|
|
|
|
expect(report.ok).toBe(false)
|
|
expect(report.failures).toContain('VITE_PADDLE_CHECKOUT_URL_OPERATOR still contains a placeholder value.')
|
|
})
|
|
|
|
it('accepts preview-tier bundle manifests with honest missing launch values', () => {
|
|
const report = buildBundleValidationReport(createPreviewManifest())
|
|
|
|
expect(report.ok).toBe(true)
|
|
expect(report.failures).toEqual([])
|
|
expect(report.warnings).toContain('VITE_PADDLE_CHECKOUT_URL_OPERATOR is not set; Operator pricing will stay on the support fallback until checkout is configured.')
|
|
expect(report.warnings).toContain('VITE_WINDOWS_DOWNLOAD_URL or WINDOWS_DOWNLOAD_URL is not set; Windows download will remain in preview posture until the release lane is configured.')
|
|
})
|
|
})
|
|
|
|
describe('renderSameOriginBundle', () => {
|
|
it('renders a runtime-ready bundle when required values are present', () => {
|
|
const rendered = renderSameOriginBundle(createValidManifest())
|
|
|
|
expect(rendered.validationReport.ok).toBe(true)
|
|
expect(rendered.frontendEnvContent).toContain('VITE_PUBLIC_DEPLOYMENT_TIER=launch')
|
|
expect(rendered.serverEnvContent).toContain('PORT=3011')
|
|
expect(rendered.frontendEnvContent).toContain('VITE_WINDOWS_DOWNLOAD_URL=https://downloads.hypertwist.app/windows.exe')
|
|
expect(rendered.serverEnvContent).toContain('RELEASE_MANIFEST_VERSION=1.0.0')
|
|
expect(rendered.systemdContent).toContain('WorkingDirectory=/srv/hypertwist/current/website/server')
|
|
expect(rendered.nginxContent).toContain('server_name hypertwist.app www.hypertwist.app;')
|
|
expect(rendered.nginxContent).toContain('server 127.0.0.1:3011;')
|
|
})
|
|
|
|
it('renders a preview-tier bundle without checkout, download, or webhook values', () => {
|
|
const rendered = renderSameOriginBundle(createPreviewManifest())
|
|
|
|
expect(rendered.validationReport.ok).toBe(true)
|
|
expect(rendered.frontendEnvContent).toContain('VITE_PUBLIC_DEPLOYMENT_TIER=preview')
|
|
expect(rendered.frontendEnvContent).toContain('VITE_PADDLE_CHECKOUT_URL_OPERATOR=')
|
|
expect(rendered.serverEnvContent).toContain('DEPLOYMENT_TIER=preview')
|
|
expect(rendered.serverEnvContent).toContain('PADDLE_WEBHOOK_SECRET=')
|
|
})
|
|
|
|
it('keeps the checked-in launch example bundle intentionally non-ready until placeholders are replaced', () => {
|
|
const { manifest } = loadBundleManifest(launchBundleExamplePath)
|
|
|
|
expect(() => renderSameOriginBundle(manifest)).toThrow(/not runtime-ready/i)
|
|
})
|
|
|
|
it('renders the checked-in preview example bundle as an honest preview-tier deployment', () => {
|
|
const { manifest } = loadBundleManifest(previewBundleExamplePath)
|
|
const rendered = renderSameOriginBundle(manifest)
|
|
|
|
expect(rendered.validationReport.ok).toBe(true)
|
|
expect(rendered.validationReport.failures).toEqual([])
|
|
expect(rendered.frontendEnv.VITE_PUBLIC_DEPLOYMENT_TIER).toBe('preview')
|
|
expect(rendered.serverEnv.DEPLOYMENT_TIER).toBe('preview')
|
|
expect(rendered.frontendEnv.VITE_MPL_SOURCE_URL).toBe('https://git.scriptoriumai.io/scriptoriumadmin/hypertwist')
|
|
expect(rendered.serverEnv.PADDLE_WEBHOOK_SECRET).toBe('')
|
|
})
|
|
})
|
|
|
|
describe('writeSameOriginBundle', () => {
|
|
it('writes all bundle outputs to the chosen directory', () => {
|
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hypertwist-bundle-render-'))
|
|
|
|
try {
|
|
const written = writeSameOriginBundle({
|
|
manifest: createValidManifest(),
|
|
outDir: tempDir,
|
|
})
|
|
|
|
expect(fs.readFileSync(written.frontendEnvPath, 'utf8')).toContain('VITE_SUPPORT_EMAIL=hello@hypertwist.app')
|
|
expect(fs.readFileSync(written.serverEnvPath, 'utf8')).toContain('PADDLE_WEBHOOK_SECRET=secret')
|
|
expect(fs.readFileSync(written.systemdPath, 'utf8')).toContain('EnvironmentFile=/srv/hypertwist/current/website/server/.env')
|
|
expect(fs.readFileSync(written.nginxPath, 'utf8')).toContain('proxy_pass http://hypertwist_website_auth_server;')
|
|
expect(JSON.parse(fs.readFileSync(written.summaryPath, 'utf8')).validationReport.ok).toBe(true)
|
|
} finally {
|
|
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
})
|