mirror of
https://github.com/ArchiveBox/ArchiveBox.git
synced 2026-09-13 18:46:17 +05:00
152 lines
6.8 KiB
Python
152 lines
6.8 KiB
Python
"""Exercise the real agent wrapper with Chromium's native storage failures."""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
from .conftest import get_free_port, run_archivebox_cmd, start_archivebox_server, stop_archivebox_process
|
|
from .test_opencode_agent import _set_archivebox_config
|
|
from .test_opencode_agent import installed_opencode as installed_opencode
|
|
from .test_opencode_agent import opencode_archive_config as opencode_archive_config
|
|
from .test_server_security_browser import browser_runtime as browser_runtime
|
|
|
|
|
|
pytestmark = pytest.mark.django_db(transaction=True)
|
|
|
|
|
|
@pytest.fixture
|
|
def agent_server(installed_opencode, browser_runtime):
|
|
port = get_free_port()
|
|
url = f"http://localhost:{port}"
|
|
config = installed_opencode.config
|
|
_set_archivebox_config(
|
|
config.data_dir,
|
|
f"BASE_URL={url}",
|
|
"SERVER_SECURITY_MODE=safe-onedomain-nojsreplay",
|
|
)
|
|
user = run_archivebox_cmd(
|
|
[
|
|
"shell",
|
|
"-c",
|
|
"from django.contrib.auth import get_user_model; get_user_model().objects.create_superuser(username='agent-browser-test', password='test-password')",
|
|
],
|
|
cwd=config.data_dir,
|
|
env=config.env,
|
|
)
|
|
assert user.returncode == 0, user.stderr or user.stdout
|
|
process = start_archivebox_server(config.data_dir, port=port, env=config.env, log_name="agent-browser-server.log")
|
|
try:
|
|
yield url, config.data_dir
|
|
finally:
|
|
stop_archivebox_process(process)
|
|
|
|
|
|
def test_agent_preserves_projects_and_survives_storage_failure(agent_server, browser_runtime):
|
|
server_url, data_dir = agent_server
|
|
script = r"""
|
|
const assert = require('node:assert/strict');
|
|
const puppeteer = require('puppeteer');
|
|
const config = JSON.parse(require('node:fs').readFileSync(0, 'utf8'));
|
|
(async () => {
|
|
for (const disabled of [false, true]) {
|
|
const browser = await puppeteer.launch({
|
|
executablePath: config.chrome,
|
|
headless: true,
|
|
args: ['--no-sandbox', ...(disabled ? ['--disable-local-storage'] : [])],
|
|
});
|
|
try {
|
|
const page = await browser.newPage();
|
|
await page.goto(new URL('/admin/login/', config.url).href, {waitUntil: 'domcontentloaded'});
|
|
await page.type('input[name="username"]', 'agent-browser-test');
|
|
await page.type('input[name="password"]', 'test-password');
|
|
await Promise.all([
|
|
page.waitForNavigation({waitUntil: 'domcontentloaded'}),
|
|
page.click('input[type="submit"], button[type="submit"]'),
|
|
]);
|
|
assert.ok(!page.url().includes('/admin/login/'), page.url());
|
|
assert.equal((await page.goto(config.url, {waitUntil: 'domcontentloaded'})).status(), 200);
|
|
const welcome = '#opencode-agent-welcome';
|
|
await page.waitForSelector(welcome, {visible: true});
|
|
if (disabled) {
|
|
assert.equal(await page.evaluate(() => {
|
|
try { return localStorage === null; }
|
|
catch (error) { if (error.name !== 'SecurityError') throw error; return true; }
|
|
}), true);
|
|
} else {
|
|
await page.click('#opencode-agent-welcome-dismiss');
|
|
await page.waitForSelector(welcome, {hidden: true});
|
|
await page.reload({waitUntil: 'domcontentloaded'});
|
|
assert.equal(await page.$eval(welcome, element => element.hidden), true);
|
|
const existing = {
|
|
list: [{type: 'http', http: {url: 'http://other.example'}}],
|
|
projects: {
|
|
local: [{worktree: '/other-project', expanded: false}],
|
|
'http://other.example': [{worktree: '/remote-project', expanded: true}],
|
|
},
|
|
lastProject: {'http://other.example': '/remote-project'},
|
|
};
|
|
await page.evaluate(value => localStorage.setItem('opencode.global.dat:server', JSON.stringify(value)), existing);
|
|
await page.reload({waitUntil: 'domcontentloaded'});
|
|
await page.waitForFunction(workdir => {
|
|
const state = JSON.parse(localStorage.getItem('opencode.global.dat:server'));
|
|
return state.projects.local?.some(project => project.worktree === workdir);
|
|
}, {}, config.workdir);
|
|
const saved = await page.evaluate(() => JSON.parse(localStorage.getItem('opencode.global.dat:server')));
|
|
assert.deepEqual(saved.list, existing.list);
|
|
assert.deepEqual(saved.projects['http://other.example'], existing.projects['http://other.example']);
|
|
assert.equal(saved.lastProject['http://other.example'], '/remote-project');
|
|
const projects = saved.projects.local;
|
|
assert.ok(Array.isArray(projects), JSON.stringify({url: page.url(), saved, existing}));
|
|
assert.deepEqual(projects.find(project => project.worktree === '/other-project'), {worktree: '/other-project', expanded: false});
|
|
assert.equal(projects.filter(project => project.worktree === config.workdir).length, 1);
|
|
await page.reload({waitUntil: 'domcontentloaded'});
|
|
assert.equal(await page.evaluate(workdir => {
|
|
const state = JSON.parse(localStorage.getItem('opencode.global.dat:server'));
|
|
return state.projects.local.filter(project => project.worktree === workdir).length;
|
|
}, config.workdir), 1);
|
|
|
|
// Fill the actual browser quota; do not replace or intercept Storage methods.
|
|
const failure = await page.evaluate(() => {
|
|
localStorage.clear();
|
|
let low = 0, high = 8 * 1024 * 1024;
|
|
while (low < high) {
|
|
const size = Math.ceil((low + high) / 2);
|
|
try { localStorage.setItem('padding', 'x'.repeat(size)); low = size; }
|
|
catch (error) { if (error.name !== 'QuotaExceededError') throw error; high = size - 1; }
|
|
}
|
|
try { localStorage.setItem('extra', '1'); return null; }
|
|
catch (error) { return error.name; }
|
|
});
|
|
assert.equal(failure, 'QuotaExceededError');
|
|
await page.reload({waitUntil: 'domcontentloaded'});
|
|
await page.waitForSelector(welcome, {visible: true});
|
|
}
|
|
await page.click('#opencode-agent-welcome-dismiss');
|
|
await page.waitForSelector(welcome, {hidden: true});
|
|
assert.equal((await page.goto(new URL('/add/', config.url).href, {waitUntil: 'domcontentloaded'})).status(), 200);
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
}
|
|
console.log('AGENT_STORAGE_FAILURE_ISOLATED');
|
|
})().catch(error => { console.error(error); process.exitCode = 1; });
|
|
"""
|
|
result = subprocess.run(
|
|
[str(browser_runtime["node_binary"]), "-e", script],
|
|
input=json.dumps(
|
|
{
|
|
"chrome": str(browser_runtime["chrome_binary"]),
|
|
"url": f"{server_url}/admin/agent",
|
|
"workdir": str(data_dir.resolve()),
|
|
},
|
|
),
|
|
env={**os.environ, "NODE_PATH": browser_runtime["node_path"]},
|
|
text=True,
|
|
capture_output=True,
|
|
timeout=120,
|
|
)
|
|
assert result.returncode == 0, result.stderr or result.stdout
|
|
assert "AGENT_STORAGE_FAILURE_ISOLATED" in result.stdout
|