Skip to content

Commit dfad664

Browse files
Merge pull request #113 from appdevforall/feat/ADFA-4511-maps-extractor
ADFA-4511 fix(dashboard): Maps Extractor — ui_bounds parsing + tolera…
2 parents e7c8133 + 1b4a2fe commit dfad664

6 files changed

Lines changed: 311 additions & 71 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Dashboard Check (typecheck + tests)
2+
3+
# The device dashboard (static/dashboard) is Node + TypeScript and boots via ts-node,
4+
# which type-checks on start — so a TS error can stop it booting. Nothing else in CI
5+
# covers it, so type-check + unit-test it on any PR that touches it. (ADFA-4511)
6+
7+
on:
8+
push:
9+
branches: [ "main", "development" ]
10+
paths:
11+
- 'static/dashboard/**'
12+
pull_request:
13+
branches: [ "main", "development" ]
14+
paths:
15+
- 'static/dashboard/**'
16+
17+
concurrency:
18+
group: ${{ github.workflow }}-${{ github.ref }}
19+
cancel-in-progress: true
20+
21+
jobs:
22+
check:
23+
name: Typecheck & Test
24+
runs-on: ubuntu-latest
25+
defaults:
26+
run:
27+
working-directory: ./static/dashboard
28+
steps:
29+
- name: Checkout
30+
uses: actions/checkout@v5
31+
32+
- name: Set up Node
33+
uses: actions/setup-node@v5
34+
with:
35+
node-version: '22'
36+
cache: 'npm'
37+
cache-dependency-path: static/dashboard/package-lock.json
38+
39+
# --ignore-scripts: skip the better-sqlite3 native build (not needed to type-check
40+
# or run the maps unit tests); still installs all JS/TS deps + devDeps.
41+
- name: Install dependencies
42+
run: npm ci --ignore-scripts
43+
44+
- name: Type-check (tsc --noEmit, strict)
45+
run: npm run typecheck
46+
47+
- name: Unit tests
48+
run: npm test

‎static/dashboard/package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"description": "",
55
"main": "index.js",
66
"scripts": {
7-
"test": "echo \"Error: no test specified\" && exit 1"
7+
"test": "node --require ts-node/register --test sockets/maps.socket.test.ts",
8+
"typecheck": "tsc --noEmit"
89
},
910
"keywords": [],
1011
"author": "AppDevForAll",
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import test from 'node:test';
2+
import assert from 'node:assert/strict';
3+
import { validateSecureCommand, parseBox, buildArgs, displayBounds } from './maps.socket';
4+
5+
const SCRIPT = '/opt/iiab/maps/tile-extract/tile-extract.py';
6+
const sudo = (rest: string) => `sudo ${SCRIPT} ${rest}`;
7+
8+
test('displayBounds prefers ui_bounds, then render_bounds/bbox/array', () => {
9+
assert.deepEqual(
10+
displayBounds({ ui_bounds: [176, -10, 184, 10], render_bounds: [-180, -10, 180, 10] }),
11+
[176, -10, 184, 10],
12+
);
13+
assert.deepEqual(displayBounds({ render_bounds: [1, 2, 3, 4] }), [1, 2, 3, 4]);
14+
assert.deepEqual(displayBounds({ bbox: [1, 2, 3, 4] }), [1, 2, 3, 4]);
15+
assert.deepEqual(displayBounds([1, 2, 3, 4]), [1, 2, 3, 4]);
16+
assert.equal(displayBounds({}), null);
17+
assert.equal(displayBounds(null), null);
18+
});
19+
20+
test('parseBox accepts a valid box and strips spaces', () => {
21+
const r = parseBox('-103.05, 19.72, -98.33, 25.84');
22+
assert.equal(r.ok, true);
23+
if (r.ok) assert.equal(r.box, '-103.05,19.72,-98.33,25.84');
24+
});
25+
26+
test('parseBox rejects bad count, empty coord, non-numeric, order, span', () => {
27+
assert.equal(parseBox('1,2,3').ok, false);
28+
assert.equal(parseBox('1,,3,4').ok, false);
29+
assert.equal(parseBox('1,x,3,4').ok, false);
30+
assert.equal(parseBox('5,2,3,4').ok, false); // minLon > maxLon
31+
assert.equal(parseBox('-200,0,200,10').ok, false); // span >= 360
32+
});
33+
34+
test('validateSecureCommand: extract with uppercase/hyphen name + spaces', () => {
35+
const v = validateSecureCommand(sudo('extract Mexico-North -103.05, 19.72, -98.33, 25.84'));
36+
assert.equal(v.type, 'extract');
37+
assert.equal(v.region, 'Mexico-North');
38+
assert.equal(v.bbox, '-103.05,19.72,-98.33,25.84');
39+
assert.equal(v.noninteractive, false);
40+
});
41+
42+
test('validateSecureCommand: extract noninteractive flag', () => {
43+
const v = validateSecureCommand(sudo('extract r1 1,2,3,4 noninteractive'));
44+
assert.equal(v.type, 'extract');
45+
assert.equal(v.noninteractive, true);
46+
});
47+
48+
test('validateSecureCommand: delete and update-json', () => {
49+
assert.equal(validateSecureCommand(sudo('delete Mexico-North')).type, 'delete');
50+
assert.equal(validateSecureCommand(sudo('update-json')).type, 'update-json');
51+
});
52+
53+
test('validateSecureCommand: rejects non-sudo, bad name, bad box', () => {
54+
assert.ok(validateSecureCommand('rm -rf /').error);
55+
assert.ok(validateSecureCommand(sudo('extract bad!name 1,2,3,4')).error);
56+
assert.ok(validateSecureCommand(sudo('extract r1 1,2,3')).error);
57+
});
58+
59+
test('buildArgs mirrors the validated command', () => {
60+
assert.deepEqual(buildArgs({ type: 'delete', region: 'r1' }), [SCRIPT, 'delete', 'r1']);
61+
assert.deepEqual(buildArgs({ type: 'update-json', region: '' }), [SCRIPT, 'update-json']);
62+
assert.deepEqual(
63+
buildArgs({ type: 'extract', region: 'r1', bbox: '1,2,3,4', noninteractive: true }),
64+
[SCRIPT, 'extract', 'r1', '1,2,3,4', 'noninteractive'],
65+
);
66+
});

0 commit comments

Comments
 (0)