chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
existsSync,
|
||||
rmSync,
|
||||
utimesSync,
|
||||
writeFileSync,
|
||||
} from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { build } from 'esbuild';
|
||||
import { test } from 'vitest';
|
||||
|
||||
async function loadHelper() {
|
||||
const tempDir = mkdtempSync(path.join(tmpdir(), 'pinme-install-helper-'));
|
||||
const outfile = path.join(tempDir, 'installProjectDependencies.cjs');
|
||||
|
||||
await build({
|
||||
entryPoints: [path.resolve('bin/utils/installProjectDependencies.ts')],
|
||||
outfile,
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
target: 'node18',
|
||||
});
|
||||
|
||||
return import(pathToFileURL(outfile).href);
|
||||
}
|
||||
|
||||
function makeProject() {
|
||||
return mkdtempSync(path.join(tmpdir(), 'pinme-install-project-'));
|
||||
}
|
||||
|
||||
test('readBackgroundInstallStatus treats fresh log-only markers as running', async () => {
|
||||
const {
|
||||
INSTALL_LOG_FILE,
|
||||
readBackgroundInstallStatus,
|
||||
} = await loadHelper();
|
||||
const projectDir = makeProject();
|
||||
|
||||
try {
|
||||
writeFileSync(path.join(projectDir, INSTALL_LOG_FILE), 'starting npm ci\n');
|
||||
|
||||
assert.deepEqual(readBackgroundInstallStatus(projectDir), {
|
||||
status: 'running',
|
||||
exitCode: null,
|
||||
logPath: path.join(projectDir, INSTALL_LOG_FILE),
|
||||
});
|
||||
} finally {
|
||||
rmSync(projectDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('readBackgroundInstallStatus detects stale log-only markers as interrupted', async () => {
|
||||
const {
|
||||
INSTALL_LOG_FILE,
|
||||
readBackgroundInstallStatus,
|
||||
} = await loadHelper();
|
||||
const projectDir = makeProject();
|
||||
const logPath = path.join(projectDir, INSTALL_LOG_FILE);
|
||||
|
||||
try {
|
||||
mkdirSync(projectDir, { recursive: true });
|
||||
writeFileSync(logPath, 'starting npm ci\n');
|
||||
const staleTime = new Date(Date.now() - 2 * 60 * 1000);
|
||||
utimesSync(logPath, staleTime, staleTime);
|
||||
|
||||
assert.deepEqual(readBackgroundInstallStatus(projectDir), {
|
||||
status: 'interrupted',
|
||||
exitCode: null,
|
||||
logPath,
|
||||
});
|
||||
} finally {
|
||||
rmSync(projectDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('readBackgroundInstallStatus detects dead background install pid as interrupted', async () => {
|
||||
const {
|
||||
INSTALL_LOG_FILE,
|
||||
INSTALL_PID_FILE,
|
||||
readBackgroundInstallStatus,
|
||||
} = await loadHelper();
|
||||
const projectDir = makeProject();
|
||||
const logPath = path.join(projectDir, INSTALL_LOG_FILE);
|
||||
|
||||
try {
|
||||
writeFileSync(logPath, 'starting npm ci\n');
|
||||
writeFileSync(path.join(projectDir, INSTALL_PID_FILE), '999999999');
|
||||
|
||||
assert.deepEqual(readBackgroundInstallStatus(projectDir), {
|
||||
status: 'interrupted',
|
||||
exitCode: null,
|
||||
logPath,
|
||||
});
|
||||
} finally {
|
||||
rmSync(projectDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('stopBackgroundInstall clears a dead background install pid marker', async () => {
|
||||
const {
|
||||
INSTALL_LOG_FILE,
|
||||
INSTALL_PID_FILE,
|
||||
stopBackgroundInstall,
|
||||
} = await loadHelper();
|
||||
const projectDir = makeProject();
|
||||
const logPath = path.join(projectDir, INSTALL_LOG_FILE);
|
||||
const pidPath = path.join(projectDir, INSTALL_PID_FILE);
|
||||
|
||||
try {
|
||||
writeFileSync(logPath, 'starting npm ci\n');
|
||||
writeFileSync(pidPath, '999999999');
|
||||
|
||||
assert.equal(await stopBackgroundInstall(projectDir), false);
|
||||
assert.equal(readFileExists(pidPath), false);
|
||||
} finally {
|
||||
rmSync(projectDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('buildBackgroundInstallCommand captures Windows npm exit code after command finishes', async () => {
|
||||
const {
|
||||
buildBackgroundInstallCommand,
|
||||
} = await loadHelper();
|
||||
|
||||
const command = buildBackgroundInstallCommand(
|
||||
'ci',
|
||||
'C:\\Users\\Pin Me\\project\\.pinme-install.log',
|
||||
'C:\\Users\\Pin Me\\project\\.pinme-install.exitcode',
|
||||
'win32',
|
||||
);
|
||||
|
||||
assert.deepEqual(command.shellArgs.slice(0, 4), ['/d', '/s', '/v:on', '/c']);
|
||||
assert.match(command.shellArgs[4], /echo !errorlevel! >/);
|
||||
assert.doesNotMatch(command.shellArgs[4], /%errorlevel%/);
|
||||
assert.match(command.shellArgs[4], /"C:\\Users\\Pin Me\\project\\.pinme-install.log"/);
|
||||
});
|
||||
|
||||
function readFileExists(filePath) {
|
||||
return existsSync(filePath);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { Readable } from 'node:stream';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { build } from 'esbuild';
|
||||
import { test } from 'vitest';
|
||||
|
||||
async function loadHelper() {
|
||||
const tempDir = mkdtempSync(path.join(tmpdir(), 'pinme-download-helper-'));
|
||||
const outfile = path.join(tempDir, 'downloadFile.cjs');
|
||||
|
||||
await build({
|
||||
entryPoints: [path.resolve('bin/utils/downloadFile.ts')],
|
||||
outfile,
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
target: 'node18',
|
||||
});
|
||||
|
||||
const helper = await import(pathToFileURL(outfile).href);
|
||||
return {
|
||||
helper,
|
||||
cleanup: () => rmSync(tempDir, { recursive: true, force: true }),
|
||||
};
|
||||
}
|
||||
|
||||
test('downloadFileWithRetries downloads through Node HTTP without curl', async () => {
|
||||
const { helper, cleanup } = await loadHelper();
|
||||
const tempDir = mkdtempSync(path.join(tmpdir(), 'pinme-download-target-'));
|
||||
const destination = path.join(tempDir, 'template.zip');
|
||||
const body = Buffer.alloc(256, 'a');
|
||||
|
||||
try {
|
||||
const result = await helper.downloadFileWithRetries('https://example.test/template.zip', destination, {
|
||||
attempts: 3,
|
||||
retryDelayMs: 1,
|
||||
minBytes: 100,
|
||||
request: async () => ({ data: Readable.from([body]) }),
|
||||
});
|
||||
|
||||
assert.equal(result.attempts, 1);
|
||||
assert.equal(result.bytes, body.length);
|
||||
assert.deepEqual(readFileSync(destination), body);
|
||||
} finally {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test('downloadFileWithRetries retries HTTP failures', async () => {
|
||||
const { helper, cleanup } = await loadHelper();
|
||||
const tempDir = mkdtempSync(path.join(tmpdir(), 'pinme-download-retry-'));
|
||||
const destination = path.join(tempDir, 'template.zip');
|
||||
const body = Buffer.alloc(256, 'b');
|
||||
let requests = 0;
|
||||
|
||||
try {
|
||||
const failures = [];
|
||||
const result = await helper.downloadFileWithRetries('https://example.test/template.zip', destination, {
|
||||
attempts: 3,
|
||||
retryDelayMs: 1,
|
||||
minBytes: 100,
|
||||
request: async () => {
|
||||
requests += 1;
|
||||
|
||||
if (requests < 3) {
|
||||
const error = new Error('Request failed with status code 503');
|
||||
error.response = {
|
||||
status: 503,
|
||||
statusText: 'Service Unavailable',
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
|
||||
return { data: Readable.from([body]) };
|
||||
},
|
||||
onAttemptFailure: (attempt, error) => {
|
||||
failures.push({ attempt, message: helper.getDownloadErrorMessage(error) });
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(result.attempts, 3);
|
||||
assert.equal(requests, 3);
|
||||
assert.deepEqual(failures, [
|
||||
{ attempt: 1, message: 'HTTP 503 Service Unavailable' },
|
||||
{ attempt: 2, message: 'HTTP 503 Service Unavailable' },
|
||||
]);
|
||||
assert.deepEqual(readFileSync(destination), body);
|
||||
} finally {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test('downloadFileWithRetries rejects tiny downloads and removes partial files', async () => {
|
||||
const { helper, cleanup } = await loadHelper();
|
||||
const tempDir = mkdtempSync(path.join(tmpdir(), 'pinme-download-small-'));
|
||||
const destination = path.join(tempDir, 'template.zip');
|
||||
|
||||
try {
|
||||
await assert.rejects(
|
||||
() => helper.downloadFileWithRetries('https://example.test/template.zip', destination, {
|
||||
attempts: 2,
|
||||
retryDelayMs: 1,
|
||||
minBytes: 100,
|
||||
request: async () => ({ data: Readable.from(['tiny']) }),
|
||||
}),
|
||||
/Downloaded file is too small/,
|
||||
);
|
||||
|
||||
assert.throws(() => readFileSync(destination), /ENOENT/);
|
||||
} finally {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,148 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { build } from 'esbuild';
|
||||
import { test } from 'vitest';
|
||||
|
||||
async function loadHelper() {
|
||||
const tempDir = mkdtempSync(path.join(tmpdir(), 'pinme-prebuilt-helper-'));
|
||||
const outfile = path.join(tempDir, 'prebuiltDistConfig.cjs');
|
||||
|
||||
await build({
|
||||
entryPoints: [path.resolve('bin/utils/prebuiltDistConfig.ts')],
|
||||
outfile,
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
target: 'node18',
|
||||
});
|
||||
|
||||
return import(pathToFileURL(outfile).href);
|
||||
}
|
||||
|
||||
function makeDist(files) {
|
||||
const distDir = mkdtempSync(path.join(tmpdir(), 'pinme-dist-'));
|
||||
|
||||
for (const [relativePath, content] of Object.entries(files)) {
|
||||
const filePath = path.join(distDir, relativePath);
|
||||
mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
writeFileSync(filePath, content);
|
||||
}
|
||||
|
||||
return distDir;
|
||||
}
|
||||
|
||||
test('patchPrebuiltFrontendDist replaces API and auth placeholders', async () => {
|
||||
const { patchPrebuiltFrontendDist } = await loadHelper();
|
||||
const distDir = makeDist({
|
||||
'index.html': '<script src="/assets/index.js"></script>',
|
||||
'assets/index.js': [
|
||||
'const api="__PINME_VITE_API_URL__";',
|
||||
'const key="__PINME_AUTH_API_KEY__";',
|
||||
'const domain="__PINME_AUTH_DOMAIN__";',
|
||||
'const project="__PINME_AUTH_PROJECT_ID__";',
|
||||
'const tenant="__PINME_TENANT_ID__";',
|
||||
].join('\n'),
|
||||
});
|
||||
|
||||
try {
|
||||
const result = patchPrebuiltFrontendDist(distDir, {
|
||||
api_domain: 'https://demo.pinme.pro',
|
||||
public_client_config: {
|
||||
auth_api_key: 'firebase-key',
|
||||
auth_domain: 'demo.firebaseapp.com',
|
||||
auth_project_id: 'firebase-project',
|
||||
tenant_id: 'tenant-123',
|
||||
},
|
||||
});
|
||||
|
||||
const bundle = readFileSync(path.join(distDir, 'assets/index.js'), 'utf8');
|
||||
assert.equal(result.apiUrlReplacements, 1);
|
||||
assert.equal(result.authReplacements, 4);
|
||||
assert.match(bundle, /https:\/\/demo\.pinme\.pro/);
|
||||
assert.match(bundle, /firebase-key/);
|
||||
assert.match(bundle, /demo\.firebaseapp\.com/);
|
||||
assert.match(bundle, /firebase-project/);
|
||||
assert.match(bundle, /tenant-123/);
|
||||
assert.doesNotMatch(bundle, /__PINME_/);
|
||||
} finally {
|
||||
rmSync(distDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('patchPrebuiltFrontendDist clears auth placeholders when auth config is absent', async () => {
|
||||
const { patchPrebuiltFrontendDist } = await loadHelper();
|
||||
const distDir = makeDist({
|
||||
'assets/index.js': [
|
||||
'const api="__PINME_VITE_API_URL__";',
|
||||
'const key="__PINME_AUTH_API_KEY__";',
|
||||
'const domain="__PINME_AUTH_DOMAIN__";',
|
||||
].join('\n'),
|
||||
});
|
||||
|
||||
try {
|
||||
const result = patchPrebuiltFrontendDist(distDir, {
|
||||
api_domain: 'https://demo.pinme.pro',
|
||||
});
|
||||
|
||||
const bundle = readFileSync(path.join(distDir, 'assets/index.js'), 'utf8');
|
||||
assert.equal(result.apiUrlReplacements, 1);
|
||||
assert.equal(result.authReplacements, 2);
|
||||
assert.equal(
|
||||
bundle,
|
||||
[
|
||||
'const api="https://demo.pinme.pro";',
|
||||
'const key="";',
|
||||
'const domain="";',
|
||||
].join('\n'),
|
||||
);
|
||||
} finally {
|
||||
rmSync(distDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('patchPrebuiltFrontendDist fails when API placeholder is missing', async () => {
|
||||
const { patchPrebuiltFrontendDist } = await loadHelper();
|
||||
const distDir = makeDist({
|
||||
'assets/index.js': 'const key="__PINME_AUTH_API_KEY__";',
|
||||
});
|
||||
|
||||
try {
|
||||
assert.throws(
|
||||
() => patchPrebuiltFrontendDist(distDir, {
|
||||
api_domain: 'https://demo.pinme.pro',
|
||||
}),
|
||||
/missing required Pinme config placeholder/,
|
||||
);
|
||||
} finally {
|
||||
rmSync(distDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('patchPrebuiltFrontendDist skips unsupported files', async () => {
|
||||
const { patchPrebuiltFrontendDist } = await loadHelper();
|
||||
const distDir = makeDist({
|
||||
'assets/index.js': 'const api="__PINME_VITE_API_URL__";',
|
||||
'assets/logo.svg': '<svg>__PINME_VITE_API_URL__</svg>',
|
||||
});
|
||||
|
||||
try {
|
||||
patchPrebuiltFrontendDist(distDir, {
|
||||
api_domain: 'https://demo.pinme.pro',
|
||||
});
|
||||
assert.equal(
|
||||
readFileSync(path.join(distDir, 'assets/logo.svg'), 'utf8'),
|
||||
'<svg>__PINME_VITE_API_URL__</svg>',
|
||||
);
|
||||
} finally {
|
||||
rmSync(distDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { build } from 'esbuild';
|
||||
import { test } from 'vitest';
|
||||
|
||||
async function loadHelper() {
|
||||
const tempDir = mkdtempSync(path.join(tmpdir(), 'pinme-tracker-helper-'));
|
||||
const outfile = path.join(tempDir, 'tracker.cjs');
|
||||
|
||||
await build({
|
||||
entryPoints: [path.resolve('bin/utils/tracker.ts')],
|
||||
outfile,
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
target: 'node18',
|
||||
});
|
||||
|
||||
const helper = await import(pathToFileURL(outfile).href);
|
||||
return {
|
||||
helper,
|
||||
cleanup: () => rmSync(tempDir, { recursive: true, force: true }),
|
||||
};
|
||||
}
|
||||
|
||||
test('getTrackErrorReason normalizes HTML API responses', async () => {
|
||||
const { helper, cleanup } = await loadHelper();
|
||||
|
||||
try {
|
||||
assert.equal(
|
||||
helper.getTrackErrorReason({
|
||||
response: {
|
||||
status: 502,
|
||||
data: '<!DOCTYPE html><html><body>Bad Gateway</body></html>',
|
||||
},
|
||||
message: 'Request failed with status code 502',
|
||||
}),
|
||||
'api_returned_html',
|
||||
);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test('getTrackErrorReason normalizes gateway status failures', async () => {
|
||||
const { helper, cleanup } = await loadHelper();
|
||||
|
||||
try {
|
||||
assert.equal(
|
||||
helper.getTrackErrorReason(new Error('Request failed with status code 520')),
|
||||
'gateway_520',
|
||||
);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test('getTrackErrorReason prefers nested cause over command wrapper messages', async () => {
|
||||
const { helper, cleanup } = await loadHelper();
|
||||
|
||||
try {
|
||||
const htmlError = {
|
||||
response: {
|
||||
status: 520,
|
||||
data: '<html>edge error</html>',
|
||||
},
|
||||
message: 'Request failed with status code 520',
|
||||
};
|
||||
const wrappedError = new Error('frontend deploy failed.');
|
||||
wrappedError.cause = htmlError;
|
||||
|
||||
assert.equal(
|
||||
helper.getTrackErrorReason(wrappedError),
|
||||
'api_returned_html',
|
||||
);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test('getTrackErrorReason normalizes authentication failures', async () => {
|
||||
const { helper, cleanup } = await loadHelper();
|
||||
|
||||
try {
|
||||
assert.equal(
|
||||
helper.getTrackErrorReason(new Error('Token authentication failed')),
|
||||
'token_auth_failed',
|
||||
);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { build } from 'esbuild';
|
||||
import { test } from 'vitest';
|
||||
|
||||
async function loadHelper() {
|
||||
const tempDir = mkdtempSync(path.join(tmpdir(), 'pinme-worker-metadata-'));
|
||||
const outfile = path.join(tempDir, 'workerMetadata.cjs');
|
||||
|
||||
await build({
|
||||
entryPoints: [path.resolve('bin/utils/workerMetadata.ts')],
|
||||
outfile,
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
target: 'node18',
|
||||
});
|
||||
|
||||
return import(pathToFileURL(outfile).href);
|
||||
}
|
||||
|
||||
function validMetadata(projectName = 'demo-project') {
|
||||
return JSON.stringify({
|
||||
main_module: 'worker.js',
|
||||
project_name: projectName,
|
||||
bindings: [
|
||||
{
|
||||
type: 'secret_text',
|
||||
name: 'API_KEY',
|
||||
text: 'real-api-key',
|
||||
},
|
||||
{
|
||||
type: 'plain_text',
|
||||
name: 'PROJECT_NAME',
|
||||
text: projectName,
|
||||
},
|
||||
],
|
||||
compatibility_date: '2024-01-01',
|
||||
});
|
||||
}
|
||||
|
||||
test('validateWorkerMetadataForCreate accepts platform metadata', async () => {
|
||||
const { validateWorkerMetadataForCreate } = await loadHelper();
|
||||
|
||||
assert.doesNotThrow(() => {
|
||||
validateWorkerMetadataForCreate(validMetadata(), 'demo-project');
|
||||
});
|
||||
});
|
||||
|
||||
test('validateWorkerMetadataForCreate rejects template placeholder metadata', async () => {
|
||||
const { validateWorkerMetadataForCreate } = await loadHelper();
|
||||
const templateMetadata = JSON.stringify({
|
||||
api_key: 'xxx',
|
||||
main_module: 'worker.js',
|
||||
project_name: 'project_name',
|
||||
compatibility_date: '2024-01-01',
|
||||
bindings: [
|
||||
{
|
||||
type: 'secret_text',
|
||||
name: 'API_KEY',
|
||||
text: 'xxx',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
() => validateWorkerMetadataForCreate(templateMetadata, 'demo-project'),
|
||||
/real API_KEY binding/,
|
||||
);
|
||||
});
|
||||
|
||||
test('validateWorkerMetadataForCreate rejects flagged API_KEY placeholder', async () => {
|
||||
const { validateWorkerMetadataForCreate } = await loadHelper();
|
||||
const metadata = JSON.stringify({
|
||||
main_module: 'worker.js',
|
||||
project_name: 'demo-project',
|
||||
bindings: [
|
||||
{
|
||||
type: 'secret_text',
|
||||
name: 'API_KEY',
|
||||
text: '__PINME_API_KEY__',
|
||||
},
|
||||
{
|
||||
type: 'plain_text',
|
||||
name: 'PROJECT_NAME',
|
||||
text: 'demo-project',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
() => validateWorkerMetadataForCreate(metadata, 'demo-project'),
|
||||
/real API_KEY binding/,
|
||||
);
|
||||
});
|
||||
|
||||
test('validateWorkerMetadataForCreate requires matching PROJECT_NAME binding', async () => {
|
||||
const { validateWorkerMetadataForCreate } = await loadHelper();
|
||||
const metadata = JSON.stringify({
|
||||
main_module: 'worker.js',
|
||||
project_name: 'demo-project',
|
||||
bindings: [
|
||||
{
|
||||
type: 'secret_text',
|
||||
name: 'API_KEY',
|
||||
text: 'real-api-key',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
() => validateWorkerMetadataForCreate(metadata, 'demo-project'),
|
||||
/PROJECT_NAME binding/,
|
||||
);
|
||||
});
|
||||
|
||||
test('validateWorkerMetadataForCreate rejects invalid JSON', async () => {
|
||||
const { validateWorkerMetadataForCreate } = await loadHelper();
|
||||
|
||||
assert.throws(
|
||||
() => validateWorkerMetadataForCreate('{not json', 'demo-project'),
|
||||
/valid JSON/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user