fix: reconcile approved live VPS source and operations
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
const { test } = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const vm = require('node:vm')
|
||||
const ts = require(process.env.TEST_TYPESCRIPT || 'typescript')
|
||||
const root = path.join(__dirname, '..')
|
||||
function load(file, mocks = {}, globals = {}) {
|
||||
const source = fs.readFileSync(path.join(root, file), 'utf8')
|
||||
const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX } }).outputText
|
||||
const module = { exports: {} }
|
||||
const jsx = (type, props) => ({ type: typeof type === 'string' ? type : 'component', ...props })
|
||||
const requireMock = id => {
|
||||
if (id in mocks) return mocks[id]
|
||||
if (id === 'react/jsx-runtime') return { jsx, jsxs: jsx, Fragment: 'fragment' }
|
||||
if (id === '@/lib/viewer') return load('src/lib/viewer.ts', mocks)
|
||||
if (id === '@/lib/launch') return load('src/lib/launch.ts')
|
||||
if (id.startsWith('node:') || ['crypto', 'util'].includes(id)) return require(id)
|
||||
if (id.endsWith('.css')) return {}
|
||||
return { __esModule: true, default: () => null }
|
||||
}
|
||||
vm.runInNewContext(output, { module, exports: module.exports, require: requireMock, process, console, ...globals }, { filename: file })
|
||||
return module.exports
|
||||
}
|
||||
const response = { NextResponse: { json: (body, options = {}) => ({ body, status: options.status || 200 }) } }
|
||||
const noIO = { getPayload: () => { throw new Error('Unexpected database access') } }
|
||||
test('free launch cannot be switched to billing by Stripe environment or date', async () => {
|
||||
const old = { ...process.env }
|
||||
try {
|
||||
for (const mode of [undefined, 'free', 'paid']) {
|
||||
if (mode) process.env.LAUNCH_MODE = mode; else delete process.env.LAUNCH_MODE
|
||||
process.env.STRIPE_SECRET_KEY = 'test-present'; process.env.STRIPE_WEBHOOK_SECRET = 'test-present'; process.env.STRIPE_PRICE_ID = 'test-present'
|
||||
const policy = load('src/lib/launch.ts')
|
||||
assert.equal(policy.billingEnabled(), false, `billing must stay disabled for env mode ${mode}`)
|
||||
assert.equal(policy.freeEarlyAccess(), true)
|
||||
const result = await load('src/app/api/subscribe/route.ts', { 'next/server': response, payload: noIO }).POST()
|
||||
assert.equal(result.status, 503); assert.ok(result.body.error); assert.equal(result.body.url, undefined)
|
||||
}
|
||||
} finally { process.env = old }
|
||||
})
|
||||
test('unverified notification signup rejects without storing email or sending mail', async () => {
|
||||
const result = await load('src/app/api/notify/route.ts', { 'next/server': response, payload: noIO }).POST(new Request('https://example.test/api/notify', { method: 'POST', body: JSON.stringify({ email: 'reader@example.test' }) }))
|
||||
assert.equal(result.status, 503); assert.ok(result.body.error); assert.equal(result.body.ok, undefined)
|
||||
})
|
||||
test('policies and public entry points describe current free reading without signup or notification form', async () => {
|
||||
for (const name of ['terms', 'privacy']) {
|
||||
const page = load(`src/app/(frontend)/${name}/page.tsx`).default()
|
||||
const text = JSON.stringify(page)
|
||||
assert.doesNotMatch(text, /مسودة|يجب إضافة|قبل الإطلاق يجب|تجريبية للواجهة/)
|
||||
assert.match(text, /2026-10-10/); assert.match(text, /Anwarpy/); assert.match(text, /hello@anwarpy.ai/)
|
||||
}
|
||||
const privacy = JSON.stringify(load('src/app/(frontend)/privacy/page.tsx').default())
|
||||
assert.match(privacy, /Cloudflare/); assert.match(privacy, /سنة واحدة/); assert.match(privacy, /تلقائي/)
|
||||
const subscription = await load('src/app/(frontend)/subscription/page.tsx', {
|
||||
payload: { getPayload: async () => ({ auth: async () => ({ user: null }), find: async () => ({ docs: [] }) }) },
|
||||
'next/headers': { headers: async () => ({}) },
|
||||
}).default()
|
||||
const rendered = JSON.stringify(subscription)
|
||||
assert.doesNotMatch(rendered, /المقدمة فقط|حساب مجاني|شهرياً|تُضاف أدلة جديدة كل شهر/)
|
||||
assert.match(rendered, /دون حساب/); assert.match(rendered, /مستقبلاً/)
|
||||
const source = fs.readFileSync(path.join(root, 'src/app/(frontend)/subscription/page.tsx'), 'utf8')
|
||||
assert.doesNotMatch(source, /NotifyForm|SubscribeButton/)
|
||||
const footer = JSON.stringify(load('src/components/footer.tsx').default())
|
||||
assert.doesNotMatch(footer, /\/signup/)
|
||||
})
|
||||
test('frontend layout displays the exact trial banner on every frontend route', () => {
|
||||
const layout = 'src/app/(frontend)/layout.tsx'
|
||||
assert.ok(fs.existsSync(path.join(root, layout)), 'shared frontend layout is required')
|
||||
const tree = JSON.stringify(load(layout).default({ children: 'page content' }))
|
||||
assert.match(tree, /اطلاق تجريبي/); assert.match(tree, /دون حساب/); assert.match(tree, /تلقائياً/)
|
||||
assert.match(tree, /aria-label/)
|
||||
})
|
||||
test('missing download payload never reports success and valid payload creates a file', () => {
|
||||
for (const key of ['missing.md', 'agent-state.md']) {
|
||||
const button = { dataset: { dl: key }, textContent: 'تنزيل', addEventListener: (event, fn) => { button.click = fn } }
|
||||
const blobs = []; let clicked = 0
|
||||
const document = { getElementById: () => null, querySelectorAll: () => [button], body: { append: () => {} }, createElement: () => ({ click: () => clicked++, remove: () => {} }) }
|
||||
const react = { useEffect: fn => fn(), useState: value => [value, () => {}] }
|
||||
load('src/app/(frontend)/posts/[slug]/post-extras.tsx', { react }, {
|
||||
document, Blob: class { constructor(body) { blobs.push(body) } }, URL: { createObjectURL: () => 'blob:test', revokeObjectURL: () => {} }, setTimeout: () => {},
|
||||
}).default({ slug: 'fixture', loggedIn: false })
|
||||
button.click({ currentTarget: button })
|
||||
if (key === 'missing.md') { assert.notEqual(button.textContent, 'تم التنزيل ✓'); assert.equal(clicked, 0); assert.equal(blobs.length, 0) }
|
||||
else { assert.equal(clicked, 1); assert.ok(blobs[0][0].length > 20); assert.equal(button.textContent, 'تم التنزيل ✓') }
|
||||
}
|
||||
})
|
||||
test('home and library entry points label intended membership as future, never require signup for reading', () => {
|
||||
for (const file of ['src/app/(frontend)/page.tsx', 'src/app/(frontend)/articles/page.tsx', 'src/app/(frontend)/topics/[topic]/page.tsx']) {
|
||||
const source = fs.readFileSync(path.join(root, file), 'utf8')
|
||||
assert.match(source, /accessLabel/)
|
||||
assert.doesNotMatch(source, /العضوية تفتح الأدلة|للأعضاء، وتُضاف/)
|
||||
}
|
||||
assert.match(fs.readFileSync(path.join(root, 'src/components/site-links.ts'), 'utf8'), /الوصول المبكر/)
|
||||
assert.doesNotMatch(fs.readFileSync(path.join(root, 'src/app/(frontend)/login/page.tsx'), 'utf8'), /\/signup/)
|
||||
})
|
||||
test('private VIP files remain protected during free launch', async () => {
|
||||
let diskRead = false
|
||||
const result = await load('src/app/api/files/[id]/route.ts', {
|
||||
'next/server': response,
|
||||
payload: { getPayload: async () => ({ findByID: async () => ({ id: 1, access: 'vip', filename: 'private.md', mimeType: 'text/plain' }) }) },
|
||||
'@/lib/viewer': { getViewer: async () => null },
|
||||
'@/collections/Files': { FILES_DIR: '/not-used' },
|
||||
'node:fs/promises': { readFile: async () => { diskRead = true; return Buffer.from('private') } },
|
||||
}).GET({}, { params: Promise.resolve({ id: '1' }) })
|
||||
assert.equal(result.status, 401); assert.equal(diskRead, false)
|
||||
})
|
||||
test('source traceability is never readable and users remain admin-created only', () => {
|
||||
const posts = load('src/collections/Posts.ts').default
|
||||
const sourceId = posts.fields.find(field => field.name === 'sourceId')
|
||||
assert.equal(typeof sourceId.access?.read, 'function')
|
||||
assert.equal(sourceId.access.read({ req: { user: null } }), false)
|
||||
assert.equal(sourceId.access.read({ req: { user: { role: 'admin' } } }), false)
|
||||
assert.equal(posts.access.read({ req: { user: null } }).status.equals, 'published')
|
||||
const users = load('src/collections/Users.ts').default
|
||||
assert.equal(users.access.create({ req: { user: null } }), false)
|
||||
assert.equal(users.access.create({ req: { user: { role: 'free' } } }), false)
|
||||
assert.equal(users.access.create({ req: { user: { role: 'admin' } } }), true)
|
||||
})
|
||||
test('unused notification and checkout controls also fail closed if reused', () => {
|
||||
const react = { useState: value => [value, () => {}] }
|
||||
const notify = JSON.stringify(load('src/app/(frontend)/subscription/notify-form.tsx', { react }).default())
|
||||
assert.doesNotMatch(notify, /\"type\":\"input\"|\"type\":\"form\"/)
|
||||
const checkout = JSON.stringify(load('src/app/(frontend)/subscription/subscribe-button.tsx', { react }).default({ loggedIn: false }))
|
||||
assert.doesNotMatch(checkout, /\/signup|اشترك/); assert.match(checkout, /\/articles/)
|
||||
assert.doesNotMatch(fs.readFileSync(path.join(root, 'src/app/(frontend)/account-settings/page.tsx'), 'utf8'), /\/signup/)
|
||||
})
|
||||
test('all source TypeScript and TSX files transpile without syntax diagnostics', () => {
|
||||
let count = 0
|
||||
function walk(dir) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = path.join(dir, entry.name)
|
||||
if (entry.isDirectory()) walk(full)
|
||||
else if (/\.tsx?$/.test(entry.name) && !entry.name.endsWith('.d.ts')) {
|
||||
const result = ts.transpileModule(fs.readFileSync(full, 'utf8'), { fileName: full, reportDiagnostics: true, compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX } })
|
||||
const errors = (result.diagnostics || []).filter(d => d.category === ts.DiagnosticCategory.Error)
|
||||
assert.equal(errors.length, 0, `${full}: ${errors.map(d => ts.flattenDiagnosticMessageText(d.messageText, '\n')).join('; ')}`)
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(path.join(root, 'src')); assert.ok(count > 50); console.log(`Syntax transpile checked ${count} source files (not full type/build validation)`)
|
||||
})
|
||||
module.exports = { load, response, noIO }
|
||||
Reference in New Issue
Block a user