• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

suculent / thinx-device-api / #252646952

14 Mar 2026 02:40PM UTC coverage: 52.509% (-18.0%) from 70.459%
#252646952

push

suculent
fix EADDRINUSE: use ephemeral port in tests, close server in afterAll hooks

- thinx-core.js: store HTTP server on this.server so specs can call close(); use port 0 in test env to avoid port 7442 collisions between test suites
- All ZZ-Router/AppSession specs: add thx.server.close() to afterAll hooks for proper resource cleanup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

1424 of 3653 branches covered (38.98%)

Branch coverage included in aggregate %.

19 of 25 new or added lines in 15 files covered. (76.0%)

2153 existing lines in 47 files now uncovered.

6036 of 10554 relevant lines covered (57.19%)

10.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

29.01
/spec/jasmine/ZZ-RouterAPIKeySpec.js
1
/* Router integration test only; does not have to cover full unit functionality. */
2

3
const THiNX = require("../../thinx-core.js");
1✔
4

5
let chai = require('chai');
1✔
6
var expect = require('chai').expect;
1✔
7
let chaiHttp = require('chai-http');
1✔
8
chai.use(chaiHttp);
1✔
9

10
//
11
// Unauthenticated
12
//
13

14
let thx;
15
let agent;
16
let jwt;
17

18
describe("API Keys (noauth)", function () {
1✔
19

20
    beforeAll((done) => {
1✔
21
        console.log(`🚸 [chai] >>> running API Keys (noauth) spec`);
1✔
22
        thx = new THiNX();
1✔
23
        thx.init(() => {
1✔
UNCOV
24
            agent = chai.request.agent(thx.app);
×
UNCOV
25
            agent
×
26
                .post('/api/login')
27
                .send({ username: 'dynamic', password: 'dynamic', remember: false })
28
                .catch((e) => { console.log(e); })
×
29
                .then(function (res) {
UNCOV
30
                    console.log(`🚸 [chai] beforeAll POST /api/login (valid) response: ${res}`);
×
UNCOV
31
                    expect(res).to.have.cookie('x-thx-core');
×
UNCOV
32
                    let body = JSON.parse(res.text);
×
UNCOV
33
                    jwt = 'Bearer ' + body.access_token;
×
UNCOV
34
                    done();
×
35
                });
36
        });
37
    });
38

39

40

41
    // create
42
    it("POST /api/user/apikey", function (done) {
1✔
43
        chai.request(thx.app)
×
44
            .post('/api/user/apikey')
45
            .send({
46
                'alias': 'mock-apikey-alias'
47
            })
48
            .end((err, res) => {
49
                expect(res.status).to.equal(401);
×
50
                done();
×
51
            });
52
    }, 30000);
53

54
    // revoke
55
    it("POST /api/user/apikey/revoke", function (done) {
1✔
56
        chai.request(thx.app)
×
57
            .post('/api/user/apikey/revoke')
58
            .send({
59
                'alias': 'mock-apikey-alias'
60
            })
61
            .end((err, res) => {
62
                expect(res.status).to.equal(401);
×
63
                done();
×
64
            });
65
    }, 30000);
66

67
    // list
68
    it("GET /api/user/apikey/list", function (done) {
1✔
69
        chai.request(thx.app)
×
70
            .get('/api/user/apikey/list')
71
            .end((err, res) => {
72
                expect(res.status).to.equal(401);
×
73
                done();
×
74
            });
75
    }, 30000);
76
});
77

78
//
79
// Authenticated
80
//
81

82

83
describe("API Keys (JWT)", function () {
1✔
84

85
    afterAll((done) => {
1✔
86
        agent.close();
1✔
NEW
87
        if (thx && thx.server) thx.server.close();
×
NEW
88
        console.log(`🚸 [chai] <<< completed API Keys (JWT) spec`);
×
UNCOV
89
        done();
×
90
    });
91

92
    var created_api_key = null;
1✔
93
    var created_api_key_2 = null;
1✔
94

95
    // create
96
    it("POST /api/user/apikey (1)", function (done) {
1✔
97
        chai.request(thx.app)
1✔
98
            .post('/api/user/apikey')
99
            .set('Authorization', jwt)
100
            .send({
101
                'alias': 'mock-apikey-alias'
102
            })
103
            .end((err, res) => {
104
                //  {"success":true,"api_key":"9b7bd4f4eacf63d8453b32dbe982eea1fb8bbc4fc8e3bcccf2fc998f96138629","hash":"0a920b2e99a917a04d7961a28b49d05524d10cd8bdc2356c026cfc1c280ca22c"}
UNCOV
105
                expect(res.status).to.equal(200);
×
UNCOV
106
                let j = JSON.parse(res.text);
×
UNCOV
107
                expect(j.success).to.equal(true);
×
UNCOV
108
                expect(j.response.api_key).to.be.a('string');
×
UNCOV
109
                expect(j.response.hash).to.be.a('string');
×
UNCOV
110
                created_api_key = j.response.hash;
×
UNCOV
111
                console.log("[spec] saving apikey (1)", j.response.api_key);
×
UNCOV
112
                done();
×
113
            });
114
    }, 30000);
115

116
    it("POST /api/user/apikey (2)", function (done) {
1✔
117
        chai.request(thx.app)
1✔
118
            .post('/api/user/apikey')
119
            .set('Authorization', jwt)
120
            .send({
121
                'alias': 'mock-apikey-alias-2'
122
            })
123
            .end((err, res) => {
UNCOV
124
                expect(res.status).to.equal(200);
×
UNCOV
125
                let j = JSON.parse(res.text);
×
UNCOV
126
                expect(j.success).to.equal(true);
×
UNCOV
127
                expect(j.response.api_key).to.be.a('string');
×
UNCOV
128
                expect(j.response.hash).to.be.a('string');
×
UNCOV
129
                console.log("[spec] saving apikey (2)", j.hash);
×
UNCOV
130
                created_api_key_2 = j.hash;
×
UNCOV
131
                done();
×
132
            });
133
    }, 30000);
134

135
    it("POST /api/user/apikey (3)", function (done) {
1✔
136
        chai.request(thx.app)
1✔
137
            .post('/api/user/apikey')
138
            .set('Authorization', jwt)
139
            .send({
140
                'alias': 'mock-apikey-alias-3'
141
            })
142
            .end((err, res) => {
UNCOV
143
                expect(res.status).to.equal(200);
×
UNCOV
144
                let j = JSON.parse(res.text);
×
UNCOV
145
                expect(j.success).to.equal(true);
×
UNCOV
146
                expect(j.response.api_key).to.be.a('string');
×
UNCOV
147
                expect(j.response.hash).to.be.a('string');
×
UNCOV
148
                done();
×
149
            });
150
    }, 30000);
151

152
    // revoke
153
    it("POST /api/user/apikey/revoke (single)", function (done) {
1✔
154
        expect(created_api_key).not.to.be.null;
1✔
UNCOV
155
        chai.request(thx.app)
×
156
            .post('/api/user/apikey/revoke')
157
            .set('Authorization', jwt)
158
            .send({
159
                'fingerprint': created_api_key
160
            })
161
            .end((err, res) => {
UNCOV
162
                expect(res.status).to.equal(200);
×
UNCOV
163
                let j = JSON.parse(res.text);
×
UNCOV
164
                expect(j.success).to.equal(true);
×
UNCOV
165
                expect(j.response).to.be.an('array');
×
UNCOV
166
                console.log(`🚸 [chai] API Keys in revocation:", ${JSON.stringify(j)} from res ${res.text}`);
×
167
                //expect(aks.length >= 1);
UNCOV
168
                done();
×
169
            });
170
    }, 30000);
171

172
    it("POST /api/user/apikey/revoke (multiple, fault)", function (done) {
1✔
173
        expect(created_api_key_2).not.to.be.null;
1✔
UNCOV
174
        chai.request(thx.app)
×
175
            .post('/api/user/apikey/revoke')
176
            .set('Authorization', jwt)
177
            .send({
178
                'fingerprints': created_api_key_2
179
            })
180
            .end((err, res) => {
181
                //  {"revoked":["7663ca65a23d759485fa158641727597256fd7eac960941fbb861ab433ab056f"],"success":true}
UNCOV
182
                console.log(`🚸 [chai] POST /api/user/apikey/revoke (multiple) response: ${res.text}, status ${res.status}`);
×
UNCOV
183
                expect(res.status).to.equal(200);
×
UNCOV
184
                let j = JSON.parse(res.text);
×
UNCOV
185
                expect(j.success).to.equal(true);
×
UNCOV
186
                expect(j.response).to.be.an('array');
×
UNCOV
187
                expect(j.response.length).to.equal(0);
×
UNCOV
188
                done();
×
189
            });
190
    }, 30000);
191

192
    it("POST /api/user/apikey/revoke (multiple)", function (done) {
1✔
193
        expect(created_api_key_2).not.to.be.null;
1✔
UNCOV
194
        chai.request(thx.app)
×
195
            .post('/api/user/apikey/revoke')
196
            .set('Authorization', jwt)
197
            .send({
198
                'fingerprints': [created_api_key_2]
199
            })
200
            .end((err, res) => {
201
                //  {"revoked":["7663ca65a23d759485fa158641727597256fd7eac960941fbb861ab433ab056f"],"success":true}
UNCOV
202
                console.log(`🚸 [chai] POST /api/user/apikey/revoke (multiple) response: ${res.text}, status ${res.status}`);
×
UNCOV
203
                expect(res.status).to.equal(200);
×
UNCOV
204
                let j = JSON.parse(res.text);
×
UNCOV
205
                expect(j.success).to.equal(true);
×
UNCOV
206
                expect(j.response).to.be.an('array');
×
207
                // TODO: fixme: does not delete anything... expect(j.revoked.length).to.equal(1);
UNCOV
208
                done();
×
209
            });
210
    }, 30000);
211

212
    // list
213
    it("GET /api/user/apikey/list", function (done) {
1✔
214
        chai.request(thx.app)
1✔
215
            .get('/api/user/apikey/list')
216
            .set('Authorization', jwt)
217
            .end((err, res) => {
UNCOV
218
                expect(res.status).to.equal(200);
×
UNCOV
219
                let j = JSON.parse(res.text);
×
UNCOV
220
                expect(j.success).to.equal(true);
×
UNCOV
221
                expect(j.response).to.be.an('array');
×
UNCOV
222
                expect(j.response.length >= 1);
×
UNCOV
223
                done();
×
224
            });
225
    }, 30000);
226

227
    // API v2
228

229
    it("POST /api/v2/apikey", function (done) {
1✔
230
        chai.request(thx.app)
1✔
231
            .post('/api/v2/apikey')
232
            .set('Authorization', jwt)
233
            .send({
234
                'alias': 'mock-apikey-alias-4'
235
            })
236
            .end((err, res) => {
UNCOV
237
                expect(res.status).to.equal(200);
×
UNCOV
238
                let j = JSON.parse(res.text);
×
UNCOV
239
                expect(j.success).to.equal(true);
×
UNCOV
240
                expect(j.response.api_key).to.be.a('string');
×
UNCOV
241
                expect(j.response.hash).to.be.a('string');
×
UNCOV
242
                done();
×
243
            });
244
    }, 30000);
245

246
    it("GET /api/v2/apikey", function (done) {
1✔
247
        chai.request(thx.app)
1✔
248
            .get('/api/v2/apikey')
249
            .set('Authorization', jwt)
250
            .end((err, res) => {
UNCOV
251
                expect(res.status).to.equal(200);
×
UNCOV
252
                let j = JSON.parse(res.text);
×
UNCOV
253
                expect(j.success).to.equal(true);
×
UNCOV
254
                expect(j.response).to.be.an('array');
×
UNCOV
255
                expect(j.response.length >= 1);
×
UNCOV
256
                done();
×
257
            });
258
    }, 30000);
259

260
    it("DELETE /api/v2/apikey", function (done) {
1✔
261
        expect(created_api_key).not.to.be.null;
1✔
UNCOV
262
        chai.request(thx.app)
×
263
            .delete('/api/v2/apikey')
264
            .set('Authorization', jwt)
265
            .send({
266
                'fingerprint': 'mock-apikey-alias-4'
267
            })
268
            .end((err, res) => {
UNCOV
269
                expect(res.status).to.equal(200);
×
UNCOV
270
                let j = JSON.parse(res.text);
×
UNCOV
271
                expect(j.success).to.equal(true);
×
UNCOV
272
                expect(j.response).to.be.an('array');
×
UNCOV
273
                console.log(`🚸 [chai] API Keys in V2 revocation:", ${JSON.stringify(j)} from res ${res.text}`);
×
274
                //expect(aks.length >= 1);
UNCOV
275
                done();
×
276
            });
277
    }, 30000);
278

279
});
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc