Administer the directory from iPX with an rwa token (#206)
A Directory feed's page offers Check now and Remove to an admin whose token is rwa (the page learns it from can_admin on the feed's page, asked of the directory only for admins). Admin, Directory lists the servers sharing, from a new GET /v1/instances, and the blocked feeds by title: a feed is often blocked because its address carries a key, so the address stays in the directory. Unblocking or dropping a snapshot sends this server's snapshot again at once; since directory's 403 reaches the page as 403, not 502. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1376,6 +1376,70 @@ test('the admin page sets up the directory, and a feed marked private leaves it
|
||||
if (paid) expect(paid.private_by_address).toBe(true);
|
||||
});
|
||||
|
||||
test('an admin removes a feed from the directory, and unblocks it (#206)', async ({ page, browser }) => {
|
||||
const all = () => page.evaluate(() => api('/api/directory'));
|
||||
const listed = async () => (await all()).map(p => p.title);
|
||||
await expect.poll(listed, { timeout: 20_000 }).toContain('Aardvark Radio');
|
||||
const id = (await all()).find(p => p.title === 'Aardvark Radio').id;
|
||||
|
||||
// Someone who is not an admin is neither offered it nor allowed it.
|
||||
const { execFileSync } = require('child_process');
|
||||
const setup = require('./global-setup');
|
||||
const env = { ...process.env, IPX_CONFIG: `${setup.root}/config/config.toml`, IPX_DATA_DIR: `${setup.root}/data` };
|
||||
try {
|
||||
execFileSync('./target/debug/ipx', ['user', 'add', 'piper'], { input: 'piperpassword', env });
|
||||
} catch (e) {
|
||||
if (!String(e.stderr || e.stdout).includes('already exists')) throw e;
|
||||
}
|
||||
const ctx = await browser.newContext();
|
||||
const piper = await ctx.newPage();
|
||||
await piper.goto('/login');
|
||||
await piper.locator('#name').fill('piper');
|
||||
await piper.locator('#pw').fill('piperpassword');
|
||||
await piper.locator('button[type=submit]').click();
|
||||
await expect(piper.locator('#feedlist')).toBeVisible();
|
||||
expect((await (await piper.request.get(`/api/directory/${id}`)).json()).can_admin).toBe(false);
|
||||
expect((await piper.request.delete(`/api/directory/${id}`)).status()).toBe(403);
|
||||
expect((await piper.request.post(`/api/directory/${id}/refresh`)).status()).toBe(403);
|
||||
expect((await piper.request.get('/api/directory-admin')).status()).toBe(403);
|
||||
expect((await piper.request.delete('/api/directory-instances/test')).status()).toBe(403);
|
||||
|
||||
// Made an admin, and subscribed to none of it, piper opens its page and is offered both. The
|
||||
// admin here subscribes to every fixture, so their tiles open their own feeds instead.
|
||||
const users = await page.evaluate(() => api('/api/users'));
|
||||
const pid = users.find(u => u.name === 'piper').id;
|
||||
const setAdmin = admin => page.evaluate(([id, admin]) => api(`/api/users/${id}`, { method: 'PATCH', body: JSON.stringify({ admin }) }), [pid, admin]);
|
||||
await setAdmin(true);
|
||||
try {
|
||||
await piper.goto('/');
|
||||
await piper.locator('#feedlist .place', { hasText: 'Directory' }).click();
|
||||
await piper.locator('#epSearch').fill('aardvark');
|
||||
await piper.locator('#dtiles .tile', { hasText: 'Aardvark Radio' }).locator('.txt').click();
|
||||
await expect(piper.locator('.show h2')).toHaveText('Aardvark Radio');
|
||||
await piper.locator('.show [data-a="drefresh"]').click();
|
||||
await expect(piper.locator('.toast', { hasText: 'Checked' })).toBeVisible();
|
||||
await expect(piper.locator('.show [data-a="dremove"]')).toHaveCount(1);
|
||||
piper.once('dialog', d => d.accept());
|
||||
await piper.locator('.show [data-a="dremove"]').click();
|
||||
await expect(piper.locator('.show h2')).toHaveCount(0);
|
||||
expect(await listed()).not.toContain('Aardvark Radio');
|
||||
} finally {
|
||||
await setAdmin(false);
|
||||
await ctx.close();
|
||||
}
|
||||
|
||||
// Admin, Directory lists the servers sharing and what is blocked, by title, never its address.
|
||||
await page.goto('/admin#directory');
|
||||
await expect(page.locator('#dadmin .urow[data-name="test"]')).toContainText(/\d+ feeds? · \d+ subscribers?/);
|
||||
const blocked = page.locator('#dadmin .urow[data-id]', { hasText: 'Aardvark Radio' });
|
||||
await expect(blocked).toBeVisible();
|
||||
expect(JSON.stringify(await page.evaluate(() => api('/api/directory-admin')))).not.toMatch(/https?:/);
|
||||
// Unblocked, it is back with the snapshot ipx sends straight away, unchanged as it is.
|
||||
await blocked.locator('[data-a="unblock"]').click();
|
||||
await expect(blocked).toHaveCount(0);
|
||||
await expect.poll(listed, { timeout: 20_000 }).toContain('Aardvark Radio');
|
||||
});
|
||||
|
||||
test('the pinned heading sits over its pins, and the page is set in Inter', async ({ page }) => {
|
||||
await page.locator('.feed', { hasText: 'Test Show' }).click();
|
||||
await expect(page.locator('#eps .ep').first()).toBeVisible({ timeout: 20_000 });
|
||||
|
||||
Reference in New Issue
Block a user