Repository navigation
Expand file tree
/
Copy pathagents.test.js
More file actions
155 lines (139 loc) · 9.19 KB
/
Copy pathagents.test.js
File metadata and controls
155 lines (139 loc) · 9.19 KB
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import {
CONNECT_SCRIPTS, connectCommand, connectNoteHtml, connectScriptsHtml, isLocalAddress, renderComputer, renderHealth,
} from '../../static/js/agents.js';
const COMPUTER = {
address: '1f3a9c20-5b1e-4d2a', version: 'v1.0.0', behind: true, offline: false,
services: [{ name: 'craft_monitor', running: true }, { name: 'network<b>', running: false }],
free_memory: 1048576, total_memory: 4194304, uptime: 3700, last_seen: Date.now() / 1000, command: null,
};
test('a computer: version, services, memory, buttons', () => {
const html = renderComputer(COMPUTER, 'v1.2.0');
assert.match(html, /Computer 1f3a9c20/);
assert.match(html, /v1\.0\.0 · update available: v1\.2\.0/);
assert.match(html, /agent-dot running"><\/span>craft_monitor</);
assert.match(html, /network<b> \(stopped\)/);
assert.match(html, /data-fill="75"/);
assert.match(html, /up 1h 1m/);
assert.doesNotMatch(html, /disabled/);
});
test('a computer missing script changes this server needs says so', () => {
const html = renderComputer({ ...COMPUTER, scripts_behind: true }, 'v1.2.0', 'v1.1.0');
assert.match(html, /scripts changed in v1\.1\.0: update recommended/);
assert.doesNotMatch(html, /update available/);
});
test('buttons wait while a command is under way, and offline', () => {
const busy = renderComputer({ ...COMPUTER, command: { action: 'reboot', ref: null, status: 'sent' } }, 'v1.2.0');
assert.match(busy, /Reboot: running…/);
assert.equal(busy.match(/ disabled/g).length, 3);
const failed = renderComputer({ ...COMPUTER, command: { action: 'update', ref: 'main', status: 'done', ok: false, message: 'HTTP 404' } });
assert.match(failed, /agent-command failed">Update to main: HTTP 404/);
assert.equal(renderComputer({ ...COMPUTER, offline: true }).match(/ disabled/g).length, 3);
});
test('a reboot: waiting for the computer, then back up', () => {
const done = { action: 'update', ref: 'release', status: 'done', ok: true, message: 'Updated v1.0.0 -> v1.2.0, rebooting.' };
const waiting = renderComputer({ ...COMPUTER, command: { ...done, awaiting_boot: true } }, 'v1.2.0');
assert.match(waiting, /rebooting, waiting for it to come back…/);
assert.equal(waiting.match(/ disabled/g).length, 3);
const back = renderComputer({ ...COMPUTER, command: { ...done, awaiting_boot: false, back_at: Date.now() / 1000 - 120 } }, 'v1.2.0');
assert.match(back, /agent-command">Update to the latest release: Updated v1\.0\.0 -> v1\.2\.0, back up 2m ago/);
assert.doesNotMatch(back, /disabled/);
const reboot = { action: 'reboot', ref: null, status: 'done', ok: true, message: 'Rebooting.', back_at: Date.now() / 1000 };
assert.match(renderComputer({ ...COMPUTER, command: reboot }), /Reboot: Back up 0s ago/);
});
test('restarted and given-up scripts, and auto-update commands', () => {
const html = renderComputer({ ...COMPUTER, services: [
{ name: 'craft_monitor', running: true, restarts: 2, gave_up: false },
{ name: 'power_monitor', running: false, restarts: 3, gave_up: true },
], command: { action: 'update', ref: 'release', status: 'queued', auto: true } });
assert.match(html, /craft_monitor \(restarted 2× this hour\)/);
assert.match(html, /<span class="agent-command failed"><span class="agent-dot"><\/span>power_monitor \(stopped, gave up restarting\)/);
assert.match(html, /Auto: update to the latest release: waiting/);
});
test('what a script died of, and one stopped on purpose', () => {
const now = Date.now() / 1000;
const html = renderComputer({ ...COMPUTER, services: [
{ name: 'craft_monitor', running: true, last_error: { message: 'not enough <memory>', at: now - 120 } },
{ name: 'power_monitor', running: true, last_error: { message: 'old', at: now - 7200 } },
{ name: 'network_browser', running: false, stopped_on_purpose: true, last_error: null },
] });
assert.match(html, /agent-line agent-command failed">craft_monitor crashed 2m ago: not enough <memory></);
assert.match(html, /<div class="agent-line">power_monitor crashed 2h ago: old</);
assert.match(html, /network_browser \(stopped on purpose\)/);
assert.doesNotMatch(html, /network_browser crashed/);
});
const HEALTH = {
uptime_seconds: 90061, gamedata: '2.7.2',
feeds: [
{ key: 'crafts', label: 'Crafting CPUs', script: 'craft_monitor.lua', age_seconds: 3, ok: true, cpus: 4 },
{ key: 'network', label: 'ME network', script: 'network_browser.lua', age_seconds: 4000, ok: false, items: 8123,
scan_seconds: 95, scanning_for: null, levels_age_seconds: 30, crash: null, reconstructed: false,
rejected: { at: 1, reason: 'incomplete (2/3 chunks, 0 errors)' } },
{ key: 'power:', label: 'Power: Main', script: 'power_monitor.lua', age_seconds: null, ok: false },
],
storage: { files: [{ name: 'app.db', bytes: 2500000 }], free_bytes: 1.2e11, total_bytes: 5e11 },
retention: { enabled: true, last_run: null },
push_subscriptions: 1,
webhooks: { configured: 2, last: { age_seconds: 120, sent: 1, failed: [{ host: 'bad.test', error: 'refused' }] } },
};
test('server health: a refused pattern scan says why', () => {
const html = renderHealth({ ...HEALTH, feeds: [
{ key: 'patterns', label: 'Patterns', script: 'network_browser.lua', age_seconds: 4000, ok: false, patterns: 4732,
off: null, rejected: { at: 1, reason: 'incomplete (237/237 chunks, 1 errors), last: getInterfaces() failed: no grid' } },
] });
assert.match(html, /agent-dot warn"><\/span><span class="health-label">Patterns/);
assert.match(html, /last scan turned away: incomplete \(237\/237 chunks, 1 errors\), last: getInterfaces\(\) failed: no grid/);
});
test('server health: each feed, storage, background jobs', () => {
const html = renderHealth(HEALTH);
assert.match(html, /agent-dot running"><\/span><span class="health-label">Crafting CPUs/);
assert.match(html, /updated 3s ago · 4 CPUs/);
assert.match(html, /agent-dot warn"><\/span><span class="health-label">ME network/);
assert.match(html, /8,123 entries · scan took 1m 35s/);
assert.match(html, /last scan turned away: incomplete \(2\/3 chunks, 0 errors\)/);
assert.match(html, /nothing from power_monitor\.lua yet/);
assert.match(html, /app\.db 2\.50MB/);
assert.match(html, /hasn't run since the server started/);
assert.match(html, /1 browser with push · 2 webhooks, last post 2m 0s ago - failed: bad\.test \(refused\)/);
assert.match(html, /Up 1d 1h · run from source · game data 2\.7\.2/);
assert.match(renderHealth({ ...HEALTH, version: 'v1.2.0' }), /Up 1d 1h · version v1\.2\.0 ·/);
assert.match(renderHealth({ ...HEALTH, version: 'v1.2.0', newer_release: 'v1.3.0' }),
/version v1\.2\.0 \(v1\.3\.0 is out: <a href="https:\/\/github\.com\/Maselkov\/gtnh-craft-monitor\/releases\/tag\/v1\.3\.0"[^>]*>what's new<\/a>\) ·/);
});
test('connect command: download, then install with the address (no trailing slash) and key', () => {
assert.equal(connectCommand(' https://gtnh.example.com/ ', 'ab12'),
'wget -f https://raw.githubusercontent.com/Maselkov/gtnh-craft-monitor/main/oc/gcm.lua /tmp/gcm.lua'
+ ' && /tmp/gcm.lua install --server=https://gtnh.example.com --api-key=ab12');
assert.ok(connectCommand('http://h:8420', 'my key;x').endsWith(' --server=http://h:8420 --api-key="my key;x"'));
});
test('connect command names the scripts picked, so gcm asks nothing', () => {
assert.ok(connectCommand('https://gtnh.example.com', 'ab12', ['craft', 'network'])
.endsWith(' && /tmp/gcm.lua install craft network --server=https://gtnh.example.com --api-key=ab12'));
});
test('connect scripts are the components oc/manifest.lua installs', () => {
const manifest = readFileSync(new URL('../../../oc/manifest.lua', import.meta.url), 'utf8');
const names = [...manifest.matchAll(/^\s*name = "(\w+)",$/gm)].map(m => m[1]);
assert.deepEqual(CONNECT_SCRIPTS.map(s => s.name), names);
});
test('connect scripts render a toggle button each, pressed as chosen, the hint as tooltip', () => {
const html = connectScriptsHtml(['power']);
assert.equal((html.match(/data-action="toggle-connect-script"/g) || []).length, 3);
assert.match(html, /class="range-btn active"[^>]*data-script="power" aria-pressed="true" title="Power tab\. Needs an Adapter touching a GregTech machine/);
assert.match(html, /class="range-btn"[^>]*data-script="craft" aria-pressed="false"/);
});
test('local addresses are the ones OpenComputers blocks by default', () => {
for (const url of ['http://localhost:8420', 'http://127.0.0.1:8420', 'http://10.1.2.3', 'http://192.168.1.20:8420', 'http://172.20.0.5']) {
assert.equal(isLocalAddress(url), true, url);
}
for (const url of ['https://gtnh.example.com', 'http://172.32.0.1', 'http://8.8.8.8', 'not a url']) {
assert.equal(isLocalAddress(url), false, url);
}
});
test('a tab\'s connect notice: its text, a button for admins, a line for everyone else', () => {
const html = connectNoteHtml('Nothing from <b>power_monitor.lua</b>');
assert.match(html, /Nothing from <b>power_monitor\.lua<\/b>/);
assert.match(html, /class="range-btn admin-only" data-action="connect-computer"/);
assert.match(html, /class="non-admin-only">An admin can connect one/);
});