forked from wangziqi/gongxue-base
173 lines
6.2 KiB
JavaScript
173 lines
6.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { pathToFileURL } from 'node:url';
|
|
|
|
const repoRoot = process.cwd();
|
|
globalThis.__TARO_PUBLIC_BUILD_CONFIG__ = {
|
|
portal: 'student',
|
|
target: 'weapp',
|
|
releaseMode: 'production',
|
|
weappTenantMode: 'fixed',
|
|
apiBaseUrl: 'https://compiled-api.gongxue100.com',
|
|
supabaseUrl: 'https://compiled-auth.gongxue100.com',
|
|
supabasePublishableKey: 'sb_publishable_compiled_key',
|
|
tenantCode: 'compiled-tenant',
|
|
};
|
|
const envModule = await import(pathToFileURL(`${repoRoot}/apps/taro/src/env.ts`).href);
|
|
delete globalThis.__TARO_PUBLIC_BUILD_CONFIG__;
|
|
|
|
assert.equal(envModule.appEnv.portal, 'student');
|
|
assert.equal(envModule.appEnv.apiBaseUrl, 'https://compiled-api.gongxue100.com');
|
|
assert.equal(envModule.appEnv.supabaseUrl, 'https://compiled-auth.gongxue100.com');
|
|
assert.equal(envModule.appEnv.supabasePublishableKey, 'sb_publishable_compiled_key');
|
|
assert.equal(envModule.appEnv.tenantCode, 'compiled-tenant');
|
|
assert.equal(envModule.taroRuntimeEnv(), 'weapp');
|
|
assert.equal(envModule.isWeappRuntime(), true);
|
|
assert.equal(envModule.taroWeappTenantMode(), 'fixed');
|
|
|
|
envModule.applyRuntimeConfig({
|
|
portal: 'tenant-admin',
|
|
apiBaseUrl: 'https://api.gongxue100.com///',
|
|
supabaseUrl: 'https://auth.gongxue100.com///',
|
|
supabasePublishableKey: 'sb_publishable_public_key',
|
|
tenantCode: 'tenant-a',
|
|
});
|
|
|
|
assert.equal(envModule.appEnv.portal, 'tenant-admin');
|
|
assert.equal(envModule.appEnv.apiBaseUrl, 'https://api.gongxue100.com');
|
|
assert.equal(envModule.appEnv.supabaseUrl, 'https://auth.gongxue100.com');
|
|
assert.equal(envModule.appEnv.supabasePublishableKey, 'sb_publishable_public_key');
|
|
assert.equal(envModule.appEnv.tenantCode, 'tenant-a');
|
|
|
|
envModule.applyRuntimeConfig({ tenantCode: '' });
|
|
assert.equal(envModule.appEnv.tenantCode, '', 'An empty runtime tenantCode must clear a compiled tenant fallback');
|
|
|
|
envModule.applyRuntimeConfig({
|
|
TARO_APP_PORTAL: 'platform-admin',
|
|
TARO_APP_API_BASE_URL: 'https://api2.gongxue100.com',
|
|
});
|
|
|
|
assert.equal(envModule.appEnv.portal, 'platform-admin');
|
|
assert.equal(envModule.appEnv.apiBaseUrl, 'https://api2.gongxue100.com');
|
|
|
|
envModule.applyRuntimeConfig({
|
|
portal: 'invalid-portal',
|
|
apiBaseUrl: '',
|
|
});
|
|
|
|
assert.equal(envModule.appEnv.portal, 'platform-admin');
|
|
assert.equal(envModule.appEnv.apiBaseUrl, 'https://api2.gongxue100.com');
|
|
|
|
assert.throws(
|
|
() => envModule.applyRuntimeConfig({
|
|
apiBaseUrl: 'https://api.gongxue100.com',
|
|
SUPABASE_SERVICE_ROLE_KEY: 'must-not-ship-to-browser',
|
|
}),
|
|
/Forbidden secret key in runtime config: SUPABASE_SERVICE_ROLE_KEY/,
|
|
);
|
|
|
|
assert.throws(
|
|
() => envModule.applyRuntimeConfig({
|
|
apiBaseUrl: 'https://api.gongxue100.com',
|
|
unexpectedFeatureFlag: 'unsafe-drift',
|
|
}),
|
|
/Unknown key in runtime config: unexpectedFeatureFlag/,
|
|
);
|
|
|
|
globalThis.__TARO_PUBLIC_BUILD_CONFIG__ = {
|
|
portal: 'student',
|
|
target: 'h5',
|
|
releaseMode: 'production',
|
|
weappTenantMode: '',
|
|
apiBaseUrl: '',
|
|
supabaseUrl: '',
|
|
supabasePublishableKey: '',
|
|
tenantCode: '',
|
|
};
|
|
globalThis.window = {
|
|
location: { origin: 'https://student.example.test' },
|
|
fetch: async () => {
|
|
throw new Error('network unavailable');
|
|
},
|
|
};
|
|
const productionH5Url = pathToFileURL(`${repoRoot}/apps/taro/src/env.ts`);
|
|
productionH5Url.searchParams.set('runtime-config-test', 'production-h5');
|
|
const productionH5Env = await import(productionH5Url.href);
|
|
assert.equal(productionH5Env.appEnv.apiBaseUrl, '', 'Production H5 must not compile a local API fallback');
|
|
assert.equal(productionH5Env.taroReleaseMode(), 'production');
|
|
await assert.rejects(
|
|
() => productionH5Env.loadRuntimeConfig(),
|
|
/Production H5 runtime-config\.json request failed: network unavailable/,
|
|
'Production H5 must fail closed when runtime config cannot be fetched',
|
|
);
|
|
globalThis.window.fetch = async () => ({ ok: false, status: 404 });
|
|
await assert.rejects(
|
|
() => productionH5Env.loadRuntimeConfig(),
|
|
/Production H5 runtime-config\.json request failed with status 404/,
|
|
'Production H5 must fail closed when runtime config is missing',
|
|
);
|
|
globalThis.window.fetch = async () => ({ ok: true, status: 200, text: async () => '' });
|
|
await assert.rejects(
|
|
() => productionH5Env.loadRuntimeConfig(),
|
|
/Production H5 runtime-config\.json is empty or unreadable/,
|
|
'Production H5 must fail closed when runtime config is unreadable',
|
|
);
|
|
globalThis.window.fetch = async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
text: async () => JSON.stringify({ portal: 'student', apiBaseUrl: '' }),
|
|
});
|
|
await assert.rejects(
|
|
() => productionH5Env.loadRuntimeConfig(),
|
|
/Production H5 runtime-config\.json apiBaseUrl must be an absolute HTTPS URL and must not use localhost or loopback/,
|
|
'Production H5 must fail closed when runtime config omits its API endpoint',
|
|
);
|
|
for (const apiBaseUrl of [
|
|
'http://api.gongxue100.com',
|
|
'https://localhost:8787',
|
|
'https://127.0.0.1:8787',
|
|
'https://[::1]:8787',
|
|
]) {
|
|
globalThis.window.fetch = async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
text: async () => JSON.stringify({ portal: 'student', apiBaseUrl }),
|
|
});
|
|
await assert.rejects(
|
|
() => productionH5Env.loadRuntimeConfig(),
|
|
/must be an absolute HTTPS URL and must not use localhost or loopback/,
|
|
`Production H5 must reject unsafe API endpoint ${apiBaseUrl}`,
|
|
);
|
|
}
|
|
globalThis.window.fetch = async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
text: async () => JSON.stringify({
|
|
portal: 'student',
|
|
apiBaseUrl: 'https://api.gongxue100.com/',
|
|
supabaseUrl: 'https://auth.gongxue100.com/',
|
|
supabasePublishableKey: 'sb_publishable_runtime_test',
|
|
tenantCode: '',
|
|
}),
|
|
});
|
|
await productionH5Env.loadRuntimeConfig();
|
|
assert.equal(productionH5Env.appEnv.apiBaseUrl, 'https://api.gongxue100.com');
|
|
assert.equal(productionH5Env.appEnv.tenantCode, '', 'Production H5 must remain domain resolved');
|
|
globalThis.window.fetch = async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
text: async () => JSON.stringify({
|
|
portal: 'student',
|
|
apiBaseUrl: 'https://api.gongxue100.com',
|
|
tenantCode: 'campus-north',
|
|
}),
|
|
});
|
|
await assert.rejects(
|
|
() => productionH5Env.loadRuntimeConfig(),
|
|
/tenantCode must be empty; tenant is resolved from the browser origin/,
|
|
'Production H5 must reject a fixed tenant override',
|
|
);
|
|
delete globalThis.window;
|
|
delete globalThis.__TARO_PUBLIC_BUILD_CONFIG__;
|
|
|
|
console.log('[PASS] Taro runtime config guardrails');
|