summaryrefslogtreecommitdiff
path: root/crates/chat-server/tests/progress.mjs
blob: 0ee56149f47c9f72a1719e7ca6a0975f41085503 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// Copyright (C) 2026 Doan Luu Duc Tai <doan@luuduct.ai>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

import assert from 'node:assert/strict';
import { createServer } from 'node:http';
import { readFile, writeFile, mkdir, mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { chromium, firefox } from '../../../.tools/browser/node_modules/playwright/index.mjs';

const libre = process.env.CHAT_LIBREJS === '1';
const profile = await mkdtemp(join(tmpdir(),'stack-chat-progress-'));
const bytes = await readFile(resolve('target/wasm32-wasip1/release/stack-chat-crypto.wasm'));
let mode = 'known';
let transfer;
let configuration;
let configReady;
let signalConfig;
let origin;
const errors = [];
const server = createServer(async (request,response) => {
    response.setHeader('Cache-Control','no-store');
    if (request.url === '/crypto.wasm') {
        if (mode === 'failure') { response.writeHead(503); response.end(); return; }
        response.setHeader('Content-Type','application/wasm');
        if (mode !== 'unknown') response.setHeader('Content-Length',bytes.length);
        transfer = response;
        response.write(bytes.subarray(0,1048576));
        return;
    }
    if (request.url === '/api') { configuration = response; signalConfig(); return; }
    const assets = { '/':'crates/chat-server/web/index.html','/app.js':'crates/chat-server/web/app.js','/style.css':'crates/chat-server/web/style.css' };
    const path = assets[request.url];
    if (!path) { response.writeHead(404); response.end(); return; }
    const mime = request.url === '/' ? 'text/html' : request.url.endsWith('.js') ? 'text/javascript' : 'text/css';
    response.setHeader('Content-Type',mime);
    let source = await readFile(resolve(path),'utf8');
    if (request.url === '/') source = source.replaceAll('{{SITE_ORIGIN}}',origin);
    response.end(source);
});
await new Promise(done => server.listen(0,'127.0.0.1',done));
origin = `http://127.0.0.1:${server.address().port}`;
let browser;
let context;
if (libre) {
    await mkdir(join(profile,'extensions'));
    const addon = await readFile(resolve('.tools/librejs.xpi'));
    await writeFile(join(profile,'extensions','jid1-KtlZuoiikVfFew@jetpack.xpi'),addon);
    context = await firefox.launchPersistentContext(profile,{headless:true,firefoxUserPrefs:{'extensions.autoDisableScopes':0,'extensions.enabledScopes':15,'extensions.startupScanScopes':15,'extensions.update.enabled':false}});
    browser = {close:() => context.close()};
} else {
    browser = await chromium.launch({headless:true,channel:'chromium'});
    context = await browser.newContext();
}
async function open(selected) {
    mode = selected;
    configuration = undefined;
    configReady = new Promise(done => { signalConfig = done; });
    const page = await context.newPage();
    page.on('pageerror',error => errors.push(error.message));
    await page.clock.install();
    await page.goto(origin);
    return page;
}
async function waitStatus(page,part) {
    await page.waitForFunction(value => document.getElementById('status').textContent.includes(value),part);
    return page.locator('#status').textContent();
}
async function complete(page,offset) {
    transfer.end(bytes.subarray(offset));
    await waitStatus(page,'Connecting to chat');
    await configReady;
    configuration.setHeader('Content-Type','application/json');
    configuration.end(JSON.stringify({origin,owner:''}));
    await page.locator('#owner-choice').waitFor({state:'visible'});
    assert.equal(await page.locator('#status').textContent(),'');
    assert.equal(await page.evaluate(async () => (await wasm).byteLength),bytes.length);
    await page.close();
}
try {
    const known = await open('known');
    assert.match(await waitStatus(known,'1.0 /'),/\[#{3}-{17}\] 19%/);
    await known.clock.fastForward(44000);
    transfer.write(bytes.subarray(1048576,2097152));
    assert.match(await waitStatus(known,'2.0 /'),/39%/);
    await known.screenshot({path:resolve('.tools/chat-download-progress.png'),fullPage:true});
    await known.clock.fastForward(44000);
    assert.ok(!(await known.locator('#status').textContent()).includes('stalled'));
    await complete(known,2097152);
    const unknown = await open('unknown');
    assert.equal(await waitStatus(unknown,'1.0 MiB received'),'Downloading cryptographic module... 1.0 MiB received');
    await complete(unknown,1048576);
    const failure = await open('failure');
    await waitStatus(failure,'HTTP 503');
    assert.equal(await failure.locator('#owner-choice').isVisible(),false);
    await failure.close();
    const stalled = await open('known');
    await waitStatus(stalled,'1.0 /');
    await stalled.clock.fastForward(45001);
    await waitStatus(stalled,'stalled for 45 seconds');
    assert.match(await stalled.locator('#status').textContent(),/reload/);
    assert.equal(await stalled.locator('#owner-choice').isVisible(),false);
    await stalled.close();
    assert.deepEqual(errors,[]);
    if (libre) {
        const state = JSON.parse(await readFile(join(profile,'extensions.json'),'utf8'));
        assert.ok(state.addons.find(addon => addon.id === 'jid1-KtlZuoiikVfFew@jetpack')?.active);
    }
    console.log(`PASS: ${libre ? 'Firefox/LibreJS' : 'Chromium'} streamed percentage/bar/MiB, unknown length, exact byte recovery, slow continuous transfer, idle timeout, HTTP failure, and connection phase.`);
} finally {
    await browser.close();
    server.closeAllConnections();
    await new Promise(done => server.close(done));
    await rm(profile,{recursive:true,force:true});
}