OPML import subscribes you; export lists only your feeds

Import predated accounts: it only added URLs missing from config.toml
and subscribed nobody. Importing another account's export did nothing
("Imported 0 feed(s)"), and a genuinely new feed had no subscriber, so
it was never scanned. Web and CLI import now share subscribe_opml,
which subscribes the caller (the CLI: the first admin) to every feed in
the file and reports new vs already-subscribed.

Export wrote the whole catalogue to anyone signed in, including other
people's private feed URLs. It now lists only your own subscriptions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0173mGu6rK18Ne7UGTwAaVJV
This commit is contained in:
2026-09-11 12:53:29 +00:00
parent 5e95557cbb
commit 8784d0a3fd
7 changed files with 156 additions and 74 deletions

View File

@@ -412,3 +412,43 @@ test('the only admin cannot be demoted or removed', async ({ page }) => {
const me = (await (await page.request.get('/api/users')).json()).find(u => u.name === 'admin');
expect((await page.request.delete(`/api/users/${me.id}`)).status()).toBe(400);
});
test('importing an OPML subscribes you, and export lists only your feeds', async ({ browser }) => {
// Regression: import only added URLs the catalogue lacked and subscribed nobody, so a feed
// someone else already had imported as nothing at all.
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', 'opal'], { input: 'opalpassword', env });
} catch (e) {
if (!String(e.stderr || e.stdout).includes('already exists')) throw e;
}
const ctx = await browser.newContext();
const page = await ctx.newPage();
await page.goto('/login');
await page.locator('#name').fill('opal');
await page.locator('#pw').fill('opalpassword');
await page.locator('button[type=submit]').click();
await expect(page.locator('#feedlist')).toContainText('No feeds.');
// Test Show is already in the catalogue, because the admin reads it.
const xml = '<opml version="2.0"><head><title>t</title></head><body>' +
'<outline text="Test Show" xmlUrl="http://127.0.0.1:8792/show.xml"/></body></opml>';
expect(await (await page.request.post('/api/opml', { data: { xml } })).json())
.toEqual({ added: 1, already: 0 });
expect(await (await page.request.post('/api/opml', { data: { xml } })).json())
.toEqual({ added: 0, already: 1 });
await page.reload();
await expect(page.locator('.feed', { hasText: 'Test Show' })).toBeVisible({ timeout: 20_000 });
// The admin also reads Picture Blog; that is not opal's to export.
const out = await (await page.request.get('/api/opml')).text();
expect(out).toContain('show.xml');
expect(out).not.toContain('pics.xml');
await ctx.close();
});