Redesign admin peering settings and catalogs
This commit is contained in:
1 parent
492477f86a
commit
da40d73f02
16 files changed
+1037
-85
No files matched your search
+51
-38
@@ -12,61 +12,74 @@ module.exports = async function testPeerUI(url, cookie) {
|
||||
const page = await context.newPage(), errors = [];
|
||||
page.on('pageerror', error => errors.push(error.message));
|
||||
try {
|
||||
await page.goto(url + '/peers.html');
|
||||
await page.getByRole('heading', { name: 'Peer catalogs', exact: true }).waitFor();
|
||||
let state = await page.evaluate(() => fetch('/api/admin/peering').then(response => response.json()));
|
||||
if (!state.identity) {
|
||||
await page.getByRole('button', { name: 'Generate local certificate' }).click();
|
||||
await page.getByText('Identity generated').waitFor();
|
||||
state = await page.evaluate(() => fetch('/api/admin/peering').then(response => response.json()));
|
||||
}
|
||||
assert.match(state.identity.fingerprint, /^[0-9a-f]{64}$/);
|
||||
await page.getByLabel('Reachable peer-gateway URL').fill('http://localhost:8080');
|
||||
await page.goto(url + '/peers.html#admin');
|
||||
await page.getByRole('heading', { name: 'Known peers' }).waitFor();
|
||||
assert.ok(await page.getByRole('link', { name: 'Peer catalogs' }).count() >= 1);
|
||||
const state = await page.evaluate(() => fetch('/api/admin/peering').then(response => response.json()));
|
||||
assert.equal(state.identity?.name, 'ComposeQA');
|
||||
await page.getByRole('button', { name: 'Add a peer' }).click();
|
||||
await page.getByRole('heading', { name: 'Add a peer' }).waitFor();
|
||||
await page.getByLabel('This server’s reachable peer gateway URL').fill('http://localhost:8080');
|
||||
await page.getByRole('button', { name: 'Create 48-hour invitation' }).click();
|
||||
assert.match(await page.getByLabel('Reachable peer-gateway URL').evaluate(el => el.validationMessage), /HTTPS peer-gateway origin/);
|
||||
await page.getByLabel('Reachable peer-gateway URL').fill('https://peer-test.example:8093');
|
||||
assert.match(await page.getByLabel('This server’s reachable peer gateway URL').evaluate(el => el.validationMessage), /HTTPS peer gateway origin/);
|
||||
await page.getByLabel('This server’s reachable peer gateway URL').fill('https://peer-test.example:8093');
|
||||
await page.getByRole('button', { name: 'Create 48-hour invitation' }).click();
|
||||
await page.waitForFunction(() => document.querySelector('textarea[aria-label="Invitation to exchange"]')?.value.includes('fingerprint'));
|
||||
const invitation = JSON.parse(await page.getByLabel('Invitation to exchange').inputValue());
|
||||
const outgoing = page.getByLabel('Invitation to send');
|
||||
await page.waitForFunction(() => document.querySelector('textarea[aria-label="Invitation to send"]')?.value.includes('fingerprint'));
|
||||
const invitation = JSON.parse(await outgoing.inputValue());
|
||||
assert.equal(invitation.url, 'https://peer-test.example:8093');
|
||||
assert.equal(invitation.fingerprint, state.identity.fingerprint);
|
||||
await page.getByLabel('Other server invitation').fill('{bad json');
|
||||
assert.match(invitation.name, /^[A-Za-z0-9]{1,25}$/);
|
||||
assert.ok(Date.parse(invitation.expiresAt) > Date.now());
|
||||
await page.getByLabel('Invitation received').fill('{bad json');
|
||||
await page.getByRole('button', { name: 'Import invitation' }).click();
|
||||
await page.getByText('Invalid invitation JSON').waitFor();
|
||||
await page.getByText('That is not valid invitation JSON.').waitFor();
|
||||
|
||||
const peerDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mc-peer-ui-'));
|
||||
let peerId;
|
||||
try {
|
||||
if (process.platform === 'win32' && !process.env.OPENSSL_BIN) process.env.OPENSSL_BIN = 'C:\\Program Files\\Git\\usr\\bin\\openssl.exe';
|
||||
const peer = new PeeringState(peerDir);
|
||||
peer.generateIdentity('UIFixturePeer');
|
||||
const fixtureInvitation = peer.createInvitation('https://fixture-peer.example:8093');
|
||||
await page.getByLabel('Other server invitation').fill(JSON.stringify(fixtureInvitation));
|
||||
await page.getByLabel('Invitation received').fill(JSON.stringify(fixtureInvitation));
|
||||
await page.getByRole('button', { name: 'Import invitation' }).click();
|
||||
await page.getByRole('heading', { name: 'UI fixture peer' }).waitFor();
|
||||
const card = page.locator('.peer-card').filter({ has: page.getByRole('heading', { name: 'UI fixture peer' }) });
|
||||
await card.getByText('Transfer limits').click();
|
||||
await card.getByText('Approved manga (0)').click();
|
||||
await card.getByText('Access grant').click();
|
||||
await card.getByLabel('Moon Garden').check();
|
||||
await card.getByText('Approved manga (1)').waitFor();
|
||||
assert.equal(await card.locator('details[open]').count(), 3, 'Saving a share must preserve all expanded sections');
|
||||
await page.getByRole('heading', { name: '<UIFixturePeer>', exact: true }).waitFor();
|
||||
const apiState = await page.evaluate(() => fetch('/api/peering').then(response => response.json()));
|
||||
peerId = apiState.peers.find(item => item.alias === 'UIFixturePeer').id;
|
||||
await page.locator('.peer-page-tabs').getByRole('link', { name: 'Peer catalogs' }).click();
|
||||
await page.getByRole('heading', { name: 'Peer catalogs' }).waitFor();
|
||||
if (!await page.getByRole('button', { name: 'Select visible' }).count()) {
|
||||
console.error('Peer catalog page content:', await page.locator('main').innerText());
|
||||
console.error('Peer catalog JS errors:', errors);
|
||||
}
|
||||
await page.getByLabel('Select peer').selectOption(peerId);
|
||||
assert.equal(await page.getByText('Setup is incomplete', { exact: false }).count(), 0, 'Sharing can be prepared before pairing');
|
||||
await page.getByRole('button', { name: 'Select visible' }).click();
|
||||
await page.getByRole('button', { name: 'Save approvals' }).click();
|
||||
await page.getByText('Approved titles updated.').waitFor();
|
||||
const saved = await page.evaluate(id => fetch('/api/admin/peering').then(r => r.json()).then(s => s.peers.find(p => p.id === id).shares.length), peerId);
|
||||
assert.equal(saved, 3, 'Bulk approval saves the selected local catalog');
|
||||
assert.equal(await page.getByRole('button', { name: 'Browse peer catalog' }).count(), 1);
|
||||
await page.getByRole('button', { name: 'Browse peer catalog' }).click();
|
||||
await page.getByText('Setup is incomplete', { exact: false }).waitFor();
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), 'Peer catalog should fit mobile width');
|
||||
} finally { fs.rmSync(peerDir, { recursive: true, force: true }); }
|
||||
assert.equal(errors.length, 0, `Browser errors: ${errors.join('; ')}`);
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), 'Peering page overflows on mobile');
|
||||
// An ordinary reader may browse, but cannot operate certificate controls.
|
||||
const created = await page.evaluate(async () => {
|
||||
const response = await fetch('/api/admin/user', { method: 'POST', headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'peer-reader-qa', password: 'disposable-reader-test', admin: false }) });
|
||||
return response.status;
|
||||
});
|
||||
|
||||
const created = await page.evaluate(async () => (await fetch('/api/admin/user', { method: 'POST', headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'peer-reader-qa', password: 'disposable-reader-test', admin: false }) })).status);
|
||||
assert.equal(created, 200);
|
||||
await page.evaluate(() => fetch('/api/logout', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' }));
|
||||
await page.evaluate(() => fetch('/api/login', { method: 'POST', headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'peer-reader-qa', password: 'disposable-reader-test' }) }));
|
||||
await page.reload();
|
||||
await page.getByRole('heading', { name: 'Peer catalogs', exact: true }).waitFor();
|
||||
assert.equal(await page.getByRole('heading', { name: 'Server identity & invitations' }).count(), 0);
|
||||
assert.equal((await page.evaluate(() => fetch('/api/admin/peering').then(response => response.status))), 403);
|
||||
await page.goto(url + '/peer-catalog.html');
|
||||
await page.getByText('Peer catalogs are available to administrators only.').waitFor();
|
||||
const denied = await page.evaluate(async () => Promise.all(['/api/admin/peering', '/api/peering', '/api/peering/catalog?peer=x', '/api/peering/cover?peer=x&manga=x']
|
||||
.map(async endpoint => (await fetch(endpoint)).status)));
|
||||
assert.deepEqual(denied, [403, 403, 403, 403], 'Every peering API remains admin-only');
|
||||
await page.goto(url + '/');
|
||||
await page.getByRole('link', { name: 'Peer catalogs' }).waitFor({ state: 'detached' });
|
||||
assert.equal(errors.length, 0, `Browser errors: ${errors.join('; ')}`);
|
||||
} finally { await browser.close(); }
|
||||
};
|
||||
Reference in new issue
Block a user