Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions packages/host/tests/helpers/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ interface Dir {

interface File {
kind: 'file';
content: string | object;
content: string | object | Uint8Array;
}

type CardAPI = typeof import('https://cardstack.com/base/card-api');
Expand Down Expand Up @@ -253,7 +253,7 @@ export class TestRealmAdapter implements RealmAdapter {

let value = content.content;

let fileRefContent = '';
let fileRefContent: string | Uint8Array = '';

if (path.endsWith('.json')) {
let cardApi = await this.#loader.import<CardAPI>(
Expand All @@ -272,6 +272,8 @@ export class TestRealmAdapter implements RealmAdapter {
} else {
fileRefContent = shimmedModuleIndicator;
}
} else if (value instanceof Uint8Array) {
fileRefContent = value;
} else {
fileRefContent = value as string;
}
Expand All @@ -291,7 +293,7 @@ export class TestRealmAdapter implements RealmAdapter {

async write(
path: LocalPath,
contents: string | object,
contents: string | object | Uint8Array,
): Promise<AdapterWriteResult> {
let segments = path.split('/');
let name = segments.pop()!;
Expand Down Expand Up @@ -326,9 +328,11 @@ export class TestRealmAdapter implements RealmAdapter {
dir.contents[name] = {
kind: 'file',
content:
typeof contents === 'string'
contents instanceof Uint8Array
? contents
: JSON.stringify(contents, null, 2),
: typeof contents === 'string'
? contents
: JSON.stringify(contents, null, 2),
};

this.postUpdateEvent(updateEvent);
Expand Down
5 changes: 4 additions & 1 deletion packages/realm-server/node-realm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ export class NodeAdapter implements RealmAdapter {
};
}

async write(path: string, contents: string): Promise<AdapterWriteResult> {
async write(
path: string,
contents: string | Uint8Array,
): Promise<AdapterWriteResult> {
let absolutePath = join(this.realmDir, path);
ensureFileSync(absolutePath);
writeFileSync(absolutePath, contents);
Expand Down
182 changes: 182 additions & 0 deletions packages/realm-server/tests/card-source-endpoints-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,188 @@ module(basename(__filename), function () {
});
});
});

module('binary file POST request', function (_hooks) {
module('public writable realm', function (hooks) {
setupPermissionedRealmAtURL(hooks, realmURL, {
permissions: {
'*': ['read', 'write'],
},
onRealmSetup,
});

let { getMessagesSince } = setupMatrixRoom(hooks, getRealmSetup);

test('serves a binary file POST request', async function (assert) {
let bytes = new Uint8Array([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0xff, 0xfe,
]);
let response = await request
.post('/test-image.png')
.set('Content-Type', 'application/octet-stream')
.send(Buffer.from(bytes));

assert.strictEqual(response.status, 204, 'HTTP 204 status');
assert.ok(
response.headers['x-created'],
'created date should be set for new binary file',
);

let filePath = join(
dir.name,
'realm_server_1',
'test',
'test-image.png',
);
assert.ok(existsSync(filePath), 'binary file exists on disk');
let fileBytes = readFileSync(filePath);
assert.deepEqual(
new Uint8Array(fileBytes),
bytes,
'file bytes match uploaded bytes',
);
});

test('creates file metadata for binary upload', async function (assert) {
let bytes = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
await request
.post('/meta-test.bin')
.set('Content-Type', 'application/octet-stream')
.send(Buffer.from(bytes));

let rows = await query(dbAdapter, [
'SELECT content_hash FROM realm_file_meta WHERE realm_url =',
param(testRealmHref),
'AND file_path =',
param('meta-test.bin'),
]);
assert.strictEqual(rows.length, 1, 'file meta row exists');
assert.ok(rows[0].content_hash, 'content hash is set');
});

test('overwrites existing binary file', async function (assert) {
let bytes1 = new Uint8Array([0x01, 0x02, 0x03]);
let bytes2 = new Uint8Array([0x04, 0x05, 0x06]);

let response1 = await request
.post('/overwrite-test.bin')
.set('Content-Type', 'application/octet-stream')
.send(Buffer.from(bytes1));
assert.strictEqual(response1.status, 204, 'first upload returns 204');

let response2 = await request
.post('/overwrite-test.bin')
.set('Content-Type', 'application/octet-stream')
.send(Buffer.from(bytes2));
assert.strictEqual(
response2.status,
204,
'second upload returns 204',
);

let filePath = join(
dir.name,
'realm_server_1',
'test',
'overwrite-test.bin',
);
let fileBytes = readFileSync(filePath);
assert.deepEqual(
new Uint8Array(fileBytes),
bytes2,
'file contains second upload bytes',
);
});

test('broadcasts realm events for binary upload', async function (assert) {
let realmEventTimestampStart = Date.now();

await request
.post('/event-test.bin')
.set('Content-Type', 'application/octet-stream')
.send(Buffer.from(new Uint8Array([0xca, 0xfe])));

await expectIncrementalIndexEvent(
`${testRealmURL}event-test.bin`,
realmEventTimestampStart,
{
assert,
getMessagesSince,
realm: testRealmHref,
},
);
});
});

module(
'public writable realm with size limit for binary',
function (hooks) {
setupPermissionedRealmAtURL(hooks, realmURL, {
permissions: {
'*': ['read', 'write'],
},
cardSizeLimitBytes: 512,
onRealmSetup,
});

test('returns 413 when binary payload exceeds size limit', async function (assert) {
let oversized = new Uint8Array(2048).fill(0xff);
let response = await request
.post('/too-large.bin')
.set('Content-Type', 'application/octet-stream')
.send(Buffer.from(oversized));

assert.strictEqual(response.status, 413, 'HTTP 413 status');
assert.strictEqual(
response.body.errors[0].title,
'Payload Too Large',
'error title is correct',
);
});
},
);

module('permissioned realm for binary', function (hooks) {
setupPermissionedRealmAtURL(hooks, realmURL, {
permissions: {
john: ['read', 'write'],
},
onRealmSetup,
});

test('401 without a JWT for binary upload', async function (assert) {
let response = await request
.post('/secret.bin')
.set('Content-Type', 'application/octet-stream')
.send(Buffer.from(new Uint8Array([0x01])));

assert.strictEqual(response.status, 401, 'HTTP 401 status');
});

test('403 without permission for binary upload', async function (assert) {
let response = await request
.post('/secret.bin')
.set('Content-Type', 'application/octet-stream')
.send(Buffer.from(new Uint8Array([0x01])))
.set('Authorization', `Bearer ${createJWT(testRealm, 'not-john')}`);

assert.strictEqual(response.status, 403, 'HTTP 403 status');
});

test('204 with permission for binary upload', async function (assert) {
let response = await request
.post('/secret.bin')
.set('Content-Type', 'application/octet-stream')
.send(Buffer.from(new Uint8Array([0x01])))
.set(
'Authorization',
`Bearer ${createJWT(testRealm, 'john', ['read', 'write'])}`,
);

assert.strictEqual(response.status, 204, 'HTTP 204 status');
});
});
});
});
});

Expand Down
Loading
Loading