CI/CD / preconditions (pull_request) Successful in 2s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 57s
CI/CD / test_and_check_app (pull_request) Successful in 6m33s
CI/CD / Publish immutable image (pull_request) Skipped
macOS checks / Native Rust tests and unsigned build (pull_request) Canceled after 0s
232 lines
8.2 KiB
JavaScript
232 lines
8.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { Buffer } from 'node:buffer';
|
|
import { createHash } from 'node:crypto';
|
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import test from 'node:test';
|
|
import { publishMacos, storageConfiguration } from '../publish-macos.mjs';
|
|
|
|
function fixture(t) {
|
|
const directory = mkdtempSync(join(tmpdir(), 'kanbangit-upload-test-'));
|
|
t.after(() => rmSync(directory, { recursive: true, force: true }));
|
|
const metadata = {
|
|
version: '1.2.3',
|
|
target: 'aarch64-apple-darwin',
|
|
filename: 'KanbanGit-1.2.3-aarch64-apple-darwin.zip',
|
|
};
|
|
const archive = Buffer.from('verified release');
|
|
const checksum = createHash('sha256').update(archive).digest('hex');
|
|
writeFileSync(join(directory, metadata.filename), archive);
|
|
writeFileSync(
|
|
join(directory, `${metadata.filename}.sha256`),
|
|
`${checksum} ${metadata.filename}\n`,
|
|
);
|
|
const env = {
|
|
S3_BUCKET: 'release-bucket',
|
|
S3_REGION: 'nbg1',
|
|
AWS_ACCESS_KEY_ID: 'test-key',
|
|
AWS_SECRET_ACCESS_KEY: 'test-secret',
|
|
};
|
|
const objects = new Map();
|
|
const events = [];
|
|
const run = (command, args) => {
|
|
assert.equal(command, 'aws');
|
|
assert.equal(args[1], 'https://nbg1.your-objectstorage.com');
|
|
const operation = args[args.indexOf('s3api') + 1];
|
|
const value = (name) => args[args.indexOf(name) + 1];
|
|
const key = value('--key');
|
|
events.push({ operation, key });
|
|
if (operation === 'get-bucket-versioning') {
|
|
return '{}';
|
|
}
|
|
if (operation === 'put-object') {
|
|
assert.equal(value('--if-none-match'), '*');
|
|
if (objects.has(key)) {
|
|
throw new Error(
|
|
'aws failed: (PreconditionFailed) object exists',
|
|
);
|
|
}
|
|
objects.set(key, readFileSync(value('--body')));
|
|
return '{}';
|
|
}
|
|
if (operation === 'get-object') {
|
|
writeFileSync(args.at(-1), objects.get(key));
|
|
return '{}';
|
|
}
|
|
throw new Error(`Unexpected S3 operation: ${operation}`);
|
|
};
|
|
const fetchPublic = async (url, options) => {
|
|
assert.equal(options.redirect, 'error');
|
|
assert.equal(
|
|
new URL(url).hostname,
|
|
'release-bucket.nbg1.your-objectstorage.com',
|
|
);
|
|
const key = new URL(url).pathname.slice(1);
|
|
events.push({ operation: 'public-get', key });
|
|
return {
|
|
ok: objects.has(key),
|
|
arrayBuffer: async () => objects.get(key),
|
|
};
|
|
};
|
|
return { directory, metadata, env, objects, events, run, fetchPublic };
|
|
}
|
|
|
|
test('uploads immutable files and writes completion record after public verification', async (t) => {
|
|
const f = fixture(t);
|
|
const url = await publishMacos(f);
|
|
assert.equal(
|
|
url,
|
|
`https://release-bucket.nbg1.your-objectstorage.com/releases/macos/aarch64-apple-darwin/1.2.3/${f.metadata.filename}`,
|
|
);
|
|
assert.deepEqual(
|
|
f.events.map((event) => event.operation),
|
|
[
|
|
'get-bucket-versioning',
|
|
'put-object',
|
|
'public-get',
|
|
'put-object',
|
|
'public-get',
|
|
'put-object',
|
|
'public-get',
|
|
],
|
|
);
|
|
assert.equal(f.objects.size, 3);
|
|
assert(f.events.at(-1).key.endsWith('/release.json'));
|
|
assert(![...f.objects.keys()].some((key) => key.includes('latest')));
|
|
assert.deepEqual(JSON.parse(f.objects.get(f.events.at(-1).key)), {
|
|
version: '1.2.3',
|
|
target: f.metadata.target,
|
|
url,
|
|
sha256: createHash('sha256').update('verified release').digest('hex'),
|
|
checksumUrl: `${url}.sha256`,
|
|
});
|
|
});
|
|
|
|
test('retry accepts byte-identical objects without replacing them', async (t) => {
|
|
const f = fixture(t);
|
|
await publishMacos(f);
|
|
const originals = [...f.objects.entries()];
|
|
await publishMacos(f);
|
|
assert.deepEqual([...f.objects.entries()], originals);
|
|
assert.equal(
|
|
f.events.filter((event) => event.operation === 'get-object').length,
|
|
3,
|
|
);
|
|
});
|
|
|
|
test('different existing release is never overwritten', async (t) => {
|
|
const f = fixture(t);
|
|
const key = `releases/macos/${f.metadata.target}/1.2.3/${f.metadata.filename}`;
|
|
f.objects.set(key, Buffer.from('previous version content'));
|
|
await assert.rejects(publishMacos(f), /bump the app version/);
|
|
assert.equal(f.objects.get(key).toString(), 'previous version content');
|
|
assert.equal(f.objects.size, 1);
|
|
});
|
|
|
|
test('checksum mismatch aborts before contacting S3', async (t) => {
|
|
const f = fixture(t);
|
|
writeFileSync(join(f.directory, f.metadata.filename), 'tampered');
|
|
await assert.rejects(publishMacos(f), /SHA-256/);
|
|
assert.equal(f.events.length, 0);
|
|
});
|
|
|
|
for (const status of ['Enabled', 'Suspended']) {
|
|
test(`rejects ${status} bucket versioning before any write`, async (t) => {
|
|
const f = fixture(t);
|
|
f.run = () => JSON.stringify({ Status: status });
|
|
await assert.rejects(publishMacos(f), /unversioned/);
|
|
assert.equal(f.objects.size, 0);
|
|
});
|
|
}
|
|
|
|
for (const operation of ['get-bucket-versioning', 'put-object', 'get-object']) {
|
|
test(`${operation} access or network failure cannot be mistaken for a missing object`, async (t) => {
|
|
const f = fixture(t);
|
|
await publishMacos(f);
|
|
const run = f.run;
|
|
f.events.length = 0;
|
|
f.run = (command, args) => {
|
|
if (args.includes(operation)) {
|
|
throw new Error('AccessDenied');
|
|
}
|
|
return run(command, args);
|
|
};
|
|
await assert.rejects(publishMacos(f), /AccessDenied/);
|
|
assert(!f.events.some((event) => event.key.endsWith('/release.json')));
|
|
});
|
|
}
|
|
|
|
for (const failure of ['private', 'wrong-content', 'network']) {
|
|
test(`public download ${failure} prevents completion record`, async (t) => {
|
|
const f = fixture(t);
|
|
f.fetchPublic = async () => {
|
|
if (failure === 'network') {
|
|
throw new Error('network failure');
|
|
}
|
|
return {
|
|
ok: failure !== 'private',
|
|
arrayBuffer: async () => Buffer.from('wrong'),
|
|
};
|
|
};
|
|
await assert.rejects(publishMacos(f));
|
|
assert(
|
|
![...f.objects.keys()].some((key) => key.endsWith('/release.json')),
|
|
);
|
|
});
|
|
}
|
|
|
|
test('a partial upload can resume with the same files', async (t) => {
|
|
const f = fixture(t);
|
|
const run = f.run;
|
|
f.run = (command, args) => {
|
|
if (
|
|
args.includes('put-object') &&
|
|
args[args.indexOf('--key') + 1].endsWith('.sha256')
|
|
) {
|
|
throw new Error('upload interrupted');
|
|
}
|
|
return run(command, args);
|
|
};
|
|
await assert.rejects(publishMacos(f), /interrupted/);
|
|
assert.equal(f.objects.size, 1);
|
|
f.run = run;
|
|
await publishMacos(f);
|
|
assert.equal(f.objects.size, 3);
|
|
});
|
|
|
|
for (const overrides of [
|
|
{ S3_REGION: '' },
|
|
{ S3_REGION: 'eu-central-1' },
|
|
{ S3_BUCKET: '' },
|
|
{ S3_BUCKET: 'other/../bucket' },
|
|
{ S3_PREFIX: '../other' },
|
|
{ S3_PUBLIC_BASE_URL: 'http://downloads.example.com' },
|
|
{ S3_PUBLIC_BASE_URL: 'https://secret@downloads.example.com' },
|
|
{ S3_PUBLIC_BASE_URL: 'https://downloads.example.com?token=secret' },
|
|
{ AWS_ACCESS_KEY_ID: '' },
|
|
{ AWS_SECRET_ACCESS_KEY: '' },
|
|
]) {
|
|
test(`invalid storage configuration is rejected before S3 access: ${JSON.stringify(overrides)}`, async (t) => {
|
|
const f = fixture(t);
|
|
f.env = { ...f.env, ...overrides };
|
|
await assert.rejects(publishMacos(f));
|
|
assert.equal(f.events.length, 0);
|
|
});
|
|
}
|
|
|
|
test('derives Hetzner endpoints and supports an explicit public proxy URL', () => {
|
|
for (const region of ['nbg1', 'fsn1', 'hel1']) {
|
|
const config = storageConfiguration({
|
|
S3_REGION: region,
|
|
S3_BUCKET: 'release-bucket',
|
|
S3_PUBLIC_BASE_URL: 'https://downloads.example.com/',
|
|
});
|
|
assert.equal(
|
|
config.endpoint,
|
|
`https://${region}.your-objectstorage.com`,
|
|
);
|
|
assert.equal(config.publicBase, 'https://downloads.example.com');
|
|
}
|
|
});
|