Skip to content

Commit ff818e4

Browse files
authored
chore: add tests for common server jsonrpc responses
1 parent 2150f27 commit ff818e4

File tree

8 files changed

+1607
-5
lines changed

8 files changed

+1607
-5
lines changed

.github/workflows/build.yml

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,42 @@ on:
99

1010
jobs:
1111

12+
test_and_lint:
13+
runs-on: macos-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Set up Go
18+
uses: actions/setup-go@v5
19+
with:
20+
go-version-file: go.mod
21+
22+
- name: Install dependencies
23+
run: |
24+
go get golang.org/x/tools/cmd/goimports@latest
25+
go get github.com/golangci/golangci-lint/cmd/golangci-lint@latest
26+
27+
- name: Lint
28+
run: |
29+
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.2.2
30+
make lint || true
31+
32+
- name: Security scan
33+
run: |
34+
go install golang.org/x/vuln/cmd/govulncheck@latest
35+
govulncheck -show verbose ./... || true
36+
37+
- name: Test
38+
run: |
39+
(cd test; npm install)
40+
make build test
41+
1242
build_on_macos:
1343

1444
runs-on: macos-latest
1545
permissions:
1646
contents: read
47+
needs: [test_and_lint]
1748
steps:
1849
- uses: actions/checkout@v4
1950

@@ -25,17 +56,12 @@ jobs:
2556
- name: Install dependencies
2657
run: |
2758
go get golang.org/x/tools/cmd/goimports@latest
28-
go get github.com/golangci/golangci-lint/cmd/golangci-lint@latest
2959
3060
- name: Set version
3161
if: github.ref_type == 'tag'
3262
run: |
3363
sed -i '' 's/version = \"dev\"/version = \"${{ github.ref_name }}\"/' main.go
3464
35-
- name: Lint
36-
run: |
37-
make lint || true
38-
3965
- name: Build
4066
run: |
4167
GOARCH=arm64 go build -ldflags="-s -w" -o mobilecli-arm64
@@ -58,6 +84,7 @@ jobs:
5884
runs-on: ubuntu-latest
5985
permissions:
6086
contents: read
87+
needs: [test_and_lint]
6188
steps:
6289
- uses: actions/checkout@v4
6390

@@ -97,6 +124,7 @@ jobs:
97124
runs-on: windows-latest
98125
permissions:
99126
contents: read
127+
needs: [test_and_lint]
100128
steps:
101129
- uses: actions/checkout@v4
102130
- name: Set up Go

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
mobilecli
22
screenshot.png
33
.DS_Store
4+
**/node_modules

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ build:
88

99
test:
1010
go test ./... -v -race
11+
(cd test; npm test)
1112

1213
lint:
1314
$(GOPATH)/bin/golangci-lint run

test/.mocharc.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"slow": 1000,
3+
"timeout": 30000
4+
}

test/common.test.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { spawn, ChildProcess, spawnSync } from 'child_process';
2+
import { describe, it, before, after } from 'mocha';
3+
import { strict as assert } from 'assert';
4+
import path from 'path';
5+
6+
describe('jsonrpc api common', () => {
7+
let serverProcess: ChildProcess;
8+
const serverUrl = 'http://localhost:12000';
9+
const timeout = 10000;
10+
11+
before(async function () {
12+
this.timeout(timeout);
13+
14+
// Start the mobilecli server
15+
serverProcess = spawn('../mobilecli', ['server', 'start'], {
16+
cwd: path.dirname(__filename),
17+
stdio: ['ignore', 'pipe', 'pipe']
18+
});
19+
20+
// Wait for server to start by polling the endpoint
21+
await waitForServer(serverUrl, 8000); // Wait up to 8 seconds for server to start
22+
});
23+
24+
after(() => {
25+
// Clean up the server process
26+
if (serverProcess) {
27+
serverProcess.kill('SIGTERM');
28+
}
29+
});
30+
31+
it('should return status "ok" from root endpoint', async () => {
32+
const response = await fetch(serverUrl);
33+
assert.equal(response.status, 200);
34+
35+
const data = await response.json();
36+
assert.deepEqual(data, { status: 'ok' });
37+
});
38+
39+
it('should return method_not_allowed for GET /rpc endpoint', async () => {
40+
const response = await fetch(`${serverUrl}/rpc`);
41+
assert.equal(response.status, 405);
42+
});
43+
44+
it('should return method_not_allowed for POST /rpc endpoint', async () => {
45+
const response = await fetch(`${serverUrl}/rpc`, { method: 'POST' });
46+
assert.equal(response.status, 200);
47+
const json = await response.json();
48+
assert.equal(json.jsonrpc, '2.0');
49+
assert.equal(json.error.code, -32700);
50+
assert.equal(json.error.data, 'expecting jsonrpc payload');
51+
});
52+
53+
it('should return error if jsonrpc is not 2.0', async () => {
54+
const response = await fetch(`${serverUrl}/rpc`, { method: 'POST', body: JSON.stringify({ jsonrpc: '1.0' }) });
55+
assert.equal(response.status, 200);
56+
const json = await response.json();
57+
assert.equal(json.jsonrpc, '2.0');
58+
assert.equal(json.error.code, -32600);
59+
assert.equal(json.error.data, "'jsonrpc' must be '2.0'");
60+
});
61+
62+
it('should return error if id is missing', async () => {
63+
const response = await fetch(`${serverUrl}/rpc`, { method: 'POST', body: JSON.stringify({ jsonrpc: '2.0', method: 'devices', params: {} }) });
64+
assert.equal(response.status, 200);
65+
const json = await response.json();
66+
assert.equal(json.jsonrpc, '2.0');
67+
assert.equal(json.error.code, -32600);
68+
assert.equal(json.error.data, "'id' field is required");
69+
});
70+
71+
false && it('should accept "devices" call without params', async () => {
72+
const response = await fetch(`${serverUrl}/rpc`, {
73+
method: 'POST',
74+
body: JSON.stringify({
75+
jsonrpc: '2.0',
76+
method: 'devices',
77+
id: 1
78+
}),
79+
signal: AbortSignal.timeout(30000),
80+
});
81+
82+
assert.equal(response.status, 200);
83+
const json = await response.json();
84+
assert.equal(json.jsonrpc, '2.0');
85+
assert.equal(json.id, 1);
86+
assert.ok(Array.isArray(json.result.devices));
87+
});
88+
89+
it('should return error for "device_info" call without params', async () => {
90+
const response = await fetch(`${serverUrl}/rpc`, {
91+
method: 'POST',
92+
body: JSON.stringify({
93+
jsonrpc: '2.0',
94+
method: 'device_info',
95+
id: 1
96+
})
97+
});
98+
99+
assert.equal(response.status, 200);
100+
const json = await response.json();
101+
assert.equal(json.jsonrpc, '2.0');
102+
assert.equal(json.id, 1);
103+
assert.equal(json.error.code, -32000);
104+
assert.equal(json.error.data, "'params' is required with fields: deviceId");
105+
});
106+
107+
const waitForServer = async (url: string, timeoutMs: number): Promise<void> => {
108+
const startTime = Date.now();
109+
const expirationTime = startTime + timeoutMs;
110+
111+
while (Date.now() < expirationTime) {
112+
try {
113+
const response = await fetch(url);
114+
if (response.status === 200) {
115+
return; // Server is ready
116+
}
117+
} catch (error) {
118+
// Server not ready yet, continue waiting
119+
}
120+
121+
await new Promise(resolve => setTimeout(resolve, 100));
122+
}
123+
124+
throw new Error(`Server did not start within ${timeoutMs}ms`);
125+
};
126+
});

0 commit comments

Comments
 (0)