• 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

27.94
/lib/thinx/oauth-github.js
1
var axios = require('axios');
1✔
2

3
var events = require('events');
1✔
4
var url = require('url');
1✔
5
var crypto = require('crypto');
1✔
6

7
module.exports = function (opts) {
1✔
8
  if (!opts.callbackURI) opts.callbackURI = '/github/callback';
30!
9
  if (!opts.loginURI) opts.loginURI = '/github/login';
30!
10
  if (typeof opts.scope === 'undefined') opts.scope = 'user';
30!
11
  var state = crypto.randomBytes(8).toString('hex');
30✔
12
  var urlObj = url.parse(opts.baseURL);
30✔
13
  urlObj.pathname = url.resolve(urlObj.pathname, opts.callbackURI);
30✔
14
  var redirectURI = url.format(urlObj);
30✔
15
  var emitter = new events.EventEmitter();
30✔
16

17
  function login(req, resp) {
UNCOV
18
    var u = 'https://github.com/login/oauth/authorize'
×
19
      + '?client_id=' + opts.githubClient
20
      + (opts.scope ? '&scope=' + opts.scope : '')
×
21
      + '&redirect_uri=' + redirectURI
22
      + '&state=' + state
23
      ;
UNCOV
24
    resp.statusCode = 302;
×
UNCOV
25
    resp.setHeader('location', u);
×
UNCOV
26
    resp.end();
×
27
  }
28

29
  function parseResponse(body) {
30
    const items = body.split("&");
×
31
    var data = null;
×
32
    for (item in items) {
×
33
      const kv = items[0].split("=");
×
34
      const key = kv[0];
×
35
      const val = kv[1];
×
36
      if (key.indexOf("access_token" !== -1)) {
×
37
        data = val;
×
38
        break;
×
39
      }
40
    }
41
    return data;
×
42
  }
43

44
  function callback(req, resp, cb) {
UNCOV
45
    var query = url.parse(req.url, true).query
×
UNCOV
46
    var code = query.code
×
UNCOV
47
    if (!code || code.length < 4) {
×
UNCOV
48
      const rbody = resp.body;
×
UNCOV
49
      console.log("[debug] [oauth-github] missing or invalid oauth code in ", {query}, {rbody});
×
UNCOV
50
      return emitter.emit('error', { error: 'missing or invalid oauth code' }, resp)
×
51
    }
52
    var u = 'https://github.com/login/oauth/access_token'
×
53
      + '?client_id=' + opts.githubClient
54
      + '&client_secret=' + opts.githubSecret
55
      + '&code=' + code
56
      + '&state=' + state
57
      ;
58

59
    (async () => {
×
60
      try {
×
61
        const body = await axios.get(u);
×
62
        //console.log("[debug] emitting event token with body", { body });
63
        const data = parseResponse(body.data);
×
64
        if (data.indexOf("gho_") !== -1) {
×
65
          emitter.emit('token', data);
×
66
          if (cb) return cb(null, data);
×
67
        } else {
68
          console.log("[debug] Invalid GitHub Response:", {body});
×
69
        }
70
      } catch (e) {
71
        console.log("axios get error:", e);
×
72
        if (cb) return cb(e);
×
73
        emitter.emit('error', null, e);
×
74
      }
75
    })()
76
  }
77

78
  emitter.login = login;
30✔
79
  emitter.callback = callback;
30✔
80
  return emitter;
30✔
81
}
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