Clarify storage limits and unify file metadata displays
This commit is contained in:
@@ -76,6 +76,13 @@ test('only the verified active hello account can view and mutate admin APIs; gue
|
||||
}
|
||||
assert.equal((await f.call(admin, '/auth/me')).json().account.administrator, true);
|
||||
assert.equal((await f.call(other, '/auth/me')).json().account.administrator, false);
|
||||
assert.deepEqual((await f.call(admin, '/admin/summary')).json().limits, {
|
||||
perFileBytes: f.cfg.maxFileBytes,
|
||||
accountBytes: f.cfg.userQuota,
|
||||
deviceBytes: f.cfg.guestQuota,
|
||||
devicesTotalBytes: f.cfg.totalGuestQuota,
|
||||
minDiskFreeBytes: f.cfg.minFreeBytes,
|
||||
});
|
||||
assert.equal((await f.call(caseAlias, '/auth/me')).json().account.administrator, false);
|
||||
assert.equal((await patch(f, other, { uploadsPaused: true })).statusCode, 403);
|
||||
assert.equal(
|
||||
|
||||
24
apps/server/test/display-format.test.js
Normal file
24
apps/server/test/display-format.test.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { formatSize, formatAdminDate } from '../../web/src/displayFormat.js';
|
||||
|
||||
test('shared display sizes use decimal KB below 1MB and switch only at MB/GB boundaries', () => {
|
||||
for (const [bytes, expected] of [
|
||||
[0, '0KB'],
|
||||
[10, '1KB 미만'],
|
||||
[1000, '1KB'],
|
||||
[938000, '938KB'],
|
||||
[999000, '999KB'],
|
||||
[1000000, '1.0MB'],
|
||||
[1100000, '1.1MB'],
|
||||
[1000000000, '1.0GB'],
|
||||
[1700000000, '1.7GB'],
|
||||
])
|
||||
assert.equal(formatSize(bytes), expected);
|
||||
});
|
||||
|
||||
test('admin dates include year and unambiguous Korea 24-hour time independent of host timezone', () => {
|
||||
assert.equal(formatAdminDate(Date.parse('2026-10-22T02:02:00Z')), '2026년 10월 22일 11:02');
|
||||
assert.equal(formatAdminDate(Date.parse('2026-10-21T15:02:00Z')), '2026년 10월 22일 00:02');
|
||||
assert.equal(formatAdminDate(null), '확인 불가');
|
||||
});
|
||||
@@ -85,6 +85,22 @@ test('region records are NAS-only, deduplicated by request, bounded and cleaned
|
||||
source: 'cloudflare',
|
||||
});
|
||||
assert.equal(json().recentLocations.length, 20);
|
||||
const info = f.service.storage.layout.ownerInfo;
|
||||
assert.equal(
|
||||
info.fileLocation(row).city,
|
||||
'Daejeon',
|
||||
'newer uploads cannot replace this file location',
|
||||
);
|
||||
info.record(row, id, { ip: '1.1.1.1', countryCode: 'JP', city: 'Tokyo' });
|
||||
assert.equal(
|
||||
info.fileLocation(row).city,
|
||||
'Daejeon',
|
||||
'idempotent retries cannot overwrite the original snapshot',
|
||||
);
|
||||
assert.equal(info.fileLocation(row).ip, '8.8.8.8');
|
||||
const publicRequest = await f.call(b, '/transfer-requests');
|
||||
assert.equal(publicRequest.body.includes('Daejeon'), false);
|
||||
assert.equal(publicRequest.body.includes('8.8.8.8'), false);
|
||||
const response = await f.app.inject({
|
||||
url: '/uploads/originals/' + row.storageKey.split('/')[0] + '/owner-info.json',
|
||||
});
|
||||
@@ -102,6 +118,93 @@ test('region records are NAS-only, deduplicated by request, bounded and cleaned
|
||||
f.service.db.update(s.files).set({ status: 'failed' }).where(eq(s.files.id, row.id)).run();
|
||||
await f.service.storage.layout.ownerInfo.cleanup();
|
||||
assert.equal(existsSync(owner.path), false);
|
||||
assert.equal(f.service.db.select().from(s.fileUploadContexts).all().length, 0);
|
||||
} finally {
|
||||
await f.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('each file keeps its own request location; legacy lookup matches its request and expired snapshots are removed', async () => {
|
||||
const f = await fixture();
|
||||
try {
|
||||
const a = await f.create(),
|
||||
b = await f.create();
|
||||
await f.space(a, b);
|
||||
f.cfg.clientIpSource = 'cloudflare';
|
||||
async function upload(country, city, ip) {
|
||||
const request = await f.call(
|
||||
a,
|
||||
'/transfer-requests',
|
||||
'POST',
|
||||
{
|
||||
targetDeviceId: b.deviceId,
|
||||
kind: 'file',
|
||||
count: 1,
|
||||
byteSize: 4,
|
||||
files: [{ name: 'test.txt', size: 4, type: 'text/plain' }],
|
||||
},
|
||||
randomUUID(),
|
||||
{ 'cf-connecting-ip': ip, 'cf-ipcountry': country, 'cf-ipcity': city },
|
||||
);
|
||||
assert.equal(request.statusCode, 200, request.body);
|
||||
const id = request.json().requestId;
|
||||
await f.call(b, `/transfer-requests/${id}/accept`, 'POST', {}, randomUUID());
|
||||
const result = await f.call(
|
||||
a,
|
||||
`/uploads?requestId=${id}&fileIndex=0`,
|
||||
'POST',
|
||||
Buffer.from(
|
||||
'--loc\r\nContent-Disposition: form-data; name="file"; filename="test.txt"\r\nContent-Type: text/plain\r\n\r\ntest\r\n--loc--\r\n',
|
||||
),
|
||||
randomUUID(),
|
||||
{ 'content-type': 'multipart/form-data; boundary=loc' },
|
||||
);
|
||||
assert.equal(result.statusCode, 200, result.body);
|
||||
return f.service.storage.fileRows(id)[0];
|
||||
}
|
||||
const first = await upload('KR', 'Daejeon', '8.8.8.8'),
|
||||
second = await upload('JP', 'Tokyo', '1.1.1.1'),
|
||||
info = f.service.storage.layout.ownerInfo;
|
||||
const locations = new Map(
|
||||
f.service.admin.files({}).items.map((file) => [file.id, file.lastLocation]),
|
||||
);
|
||||
assert.equal(locations.get(first.id).city, 'Daejeon');
|
||||
assert.equal(locations.get(second.id).city, 'Tokyo');
|
||||
f.service.db
|
||||
.delete(s.fileUploadContexts)
|
||||
.where(eq(s.fileUploadContexts.fileId, first.id))
|
||||
.run();
|
||||
assert.equal(
|
||||
info.fileLocation(first).city,
|
||||
'Daejeon',
|
||||
'recover only the matching legacy request',
|
||||
);
|
||||
f.service.db
|
||||
.delete(s.fileUploadContexts)
|
||||
.where(eq(s.fileUploadContexts.fileId, first.id))
|
||||
.run();
|
||||
const owner = info.details(first.storageKey);
|
||||
info.write(
|
||||
owner,
|
||||
info.read(owner).events.filter((event) => event.requestId !== first.requestId),
|
||||
);
|
||||
assert.equal(
|
||||
info.fileLocation(first),
|
||||
null,
|
||||
'a missing old record must not use the newest location',
|
||||
);
|
||||
f.service.db
|
||||
.update(s.fileUploadContexts)
|
||||
.set({ expiresAt: Date.now() - 1 })
|
||||
.run();
|
||||
const old = info.read(owner).events.map((event) => ({
|
||||
...event,
|
||||
recordedAt: new Date(Date.now() - 8 * 86400000).toISOString(),
|
||||
}));
|
||||
info.write(owner, old);
|
||||
await info.cleanup();
|
||||
assert.equal(f.service.db.select().from(s.fileUploadContexts).all().length, 0);
|
||||
assert.equal(info.fileLocation(second), null);
|
||||
} finally {
|
||||
await f.close();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user