Skip to content

Commit 307be87

Browse files
add more methods and tests
1 parent 7e6da8f commit 307be87

13 files changed

Lines changed: 329 additions & 14 deletions

bin/testingbot

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,12 @@ Authentication:
5656
Commands:
5757
user User account operations
5858
info Get current user information
59+
keys Get your API key and secret
5960
update <data> Update user information (JSON string)
6061
62+
config Account configuration
63+
ip-ranges List TestingBot IP ranges (for firewall allow-lists)
64+
6165
tests Test management operations
6266
list [offset] [limit] List tests (default: offset=0, limit=10)
6367
get <id> Get test details by ID
@@ -71,7 +75,7 @@ Commands:
7175
get <id> Get specific device details
7276
7377
browsers Browser management
74-
list [type] List browsers (type: all|web|mobile|real)
78+
list [type] List browsers (type: webdriver|rc; default webdriver)
7579
7680
storage File storage operations
7781
upload <file> Upload a file to TestingBot storage
@@ -85,9 +89,11 @@ Commands:
8589
list [offset] [limit] List screenshots
8690
8791
tunnel Tunnel management
88-
info Get tunnel information
92+
info Get the active tunnel
8993
list List all tunnels
90-
delete <id> Delete a tunnel
94+
get <id> Get a specific tunnel by ID
95+
delete <id> Delete a tunnel by ID
96+
delete-active Delete whichever tunnel is currently active
9197
9298
builds Build management
9399
list [offset] [limit] List builds
@@ -98,6 +104,7 @@ Commands:
98104
info Get team information
99105
users List users in team
100106
get-user <id> Get specific user from team
107+
client-key <id> Get a sub-account's client key
101108
create-user <data> Create user (data as JSON)
102109
103110
session Session operations
@@ -129,6 +136,10 @@ const commands = {
129136
checkCredentials();
130137
api.getUserInfo(outputResult);
131138
},
139+
keys: () => {
140+
checkCredentials();
141+
api.getUserKeys(outputResult);
142+
},
132143
update: (data) => {
133144
checkCredentials();
134145
if (!data) {
@@ -210,10 +221,10 @@ const commands = {
210221
},
211222

212223
browsers: {
213-
list: (type = 'all') => {
224+
list: (type) => {
214225
checkCredentials();
215-
if (!['all', 'web', 'mobile', 'real'].includes(type)) {
216-
console.error('Error: Invalid browser type. Use: all, web, mobile, or real');
226+
if (type && !['webdriver', 'rc'].includes(type)) {
227+
console.error('Error: Invalid browser type. Use: webdriver or rc');
217228
process.exit(1);
218229
}
219230
api.getBrowsers(type, outputResult);
@@ -309,13 +320,32 @@ const commands = {
309320
checkCredentials();
310321
api.getTunnelList(outputResult);
311322
},
323+
get: (id) => {
324+
checkCredentials();
325+
if (!id) {
326+
console.error('Error: Tunnel ID required');
327+
process.exit(1);
328+
}
329+
api.getTunnelById(id, outputResult);
330+
},
312331
delete: (id) => {
313332
checkCredentials();
314333
if (!id) {
315334
console.error('Error: Tunnel ID required');
316335
process.exit(1);
317336
}
318337
api.deleteTunnel(id, outputResult);
338+
},
339+
'delete-active': () => {
340+
checkCredentials();
341+
api.deleteActiveTunnel(outputResult);
342+
}
343+
},
344+
345+
config: {
346+
'ip-ranges': () => {
347+
checkCredentials();
348+
api.getIpRanges(outputResult);
319349
}
320350
},
321351

@@ -359,6 +389,14 @@ const commands = {
359389
}
360390
api.getUserFromTeam(id, outputResult);
361391
},
392+
'client-key': (id) => {
393+
checkCredentials();
394+
if (!id) {
395+
console.error('Error: User ID required');
396+
process.exit(1);
397+
}
398+
api.getUserClientKey(id, outputResult);
399+
},
362400
'create-user': (data) => {
363401
checkCredentials();
364402
if (!data) {

index.d.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ declare module 'testingbot-api' {
147147

148148
// Browser & Device Management
149149
getBrowsers(): Promise<Browser[]>;
150-
getBrowsers(type: 'web' | 'mobile'): Promise<Browser[]>;
150+
getBrowsers(type: 'webdriver' | 'rc'): Promise<Browser[]>;
151151
getBrowsers(callback: Callback<Browser[]>): void;
152-
getBrowsers(type: 'web' | 'mobile', callback: Callback<Browser[]>): void;
152+
getBrowsers(type: 'webdriver' | 'rc', callback: Callback<Browser[]>): void;
153153

154154
getDevices(): Promise<Device[]>;
155155
getDevices(callback: Callback<Device[]>): void;
@@ -160,6 +160,10 @@ declare module 'testingbot-api' {
160160
getDevice(deviceId: string): Promise<Device>;
161161
getDevice(deviceId: string, callback: Callback<Device>): void;
162162

163+
// Configuration
164+
getIpRanges(): Promise<string[]>;
165+
getIpRanges(callback: Callback<string[]>): void;
166+
163167
// Session Management
164168
createSession(options: SessionOptions): Promise<SessionResponse>;
165169
createSession(options: SessionOptions, callback: Callback<SessionResponse>): void;
@@ -168,6 +172,9 @@ declare module 'testingbot-api' {
168172
getUserInfo(): Promise<UserInfo>;
169173
getUserInfo(callback: Callback<UserInfo>): void;
170174

175+
getUserKeys(): Promise<{ key: string; secret: string }>;
176+
getUserKeys(callback: Callback<{ key: string; secret: string }>): void;
177+
171178
updateUserInfo(userData: UserUpdate): Promise<UserInfo>;
172179
updateUserInfo(userData: UserUpdate, callback: Callback<UserInfo>): void;
173180

@@ -193,9 +200,15 @@ declare module 'testingbot-api' {
193200
getTunnelList(): Promise<Tunnel[]>;
194201
getTunnelList(callback: Callback<Tunnel[]>): void;
195202

203+
getTunnelById(tunnelId: string | number): Promise<Tunnel>;
204+
getTunnelById(tunnelId: string | number, callback: Callback<Tunnel>): void;
205+
196206
deleteTunnel(tunnelId: string): Promise<boolean>;
197207
deleteTunnel(tunnelId: string, callback: Callback<boolean>): void;
198208

209+
deleteActiveTunnel(): Promise<boolean>;
210+
deleteActiveTunnel(callback: Callback<boolean>): void;
211+
199212
// Build Management
200213
getBuilds(): Promise<Paginated<Build>>;
201214
getBuilds(offset: number, limit: number): Promise<Paginated<Build>>;
@@ -272,6 +285,9 @@ declare module 'testingbot-api' {
272285
resetCredentials(userId: string | number): Promise<any>;
273286
resetCredentials(userId: string | number, callback: Callback<any>): void;
274287

288+
getUserClientKey(userId: string | number): Promise<{ client_key: string }>;
289+
getUserClientKey(userId: string | number, callback: Callback<{ client_key: string }>): void;
290+
275291
// Utility
276292
getAuthenticationHashForSharing(sessionId: string): string;
277293

lib/api.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const errors = require('./errors');
1010

1111
Object.assign(
1212
TestingBot.prototype,
13+
require('./resources/configuration'),
1314
require('./resources/devices'),
1415
require('./resources/browsers'),
1516
require('./resources/tests'),

lib/resources/configuration.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict';
2+
3+
module.exports = {
4+
// Returns the list of TestingBot IPv4 addresses (for firewall allow-lists).
5+
// Responds with a bare array of strings; no envelope.
6+
getIpRanges (callback) {
7+
return this.request({
8+
method: 'GET',
9+
url: '/configuration/ip-ranges'
10+
}, callback);
11+
}
12+
};

lib/resources/team.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,16 @@ module.exports = {
5252
method: 'POST',
5353
url: '/team-management/users/' + userId + '/reset-keys'
5454
}, callback);
55+
},
56+
57+
// Admin-only: fetch a sub-account's client key. Returns { client_key }.
58+
getUserClientKey (userId, callback) {
59+
if (!userId) {
60+
throw new Error('User ID is required');
61+
}
62+
return this.request({
63+
method: 'GET',
64+
url: '/team-management/users/' + userId + '/client-key'
65+
}, callback);
5566
}
5667
};

lib/resources/tunnels.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ module.exports = {
1515
}, callback);
1616
},
1717

18+
getTunnelById (tunnelId, callback) {
19+
if (!tunnelId) {
20+
throw new Error('Tunnel ID is required');
21+
}
22+
return this.request({
23+
method: 'GET',
24+
url: '/tunnel/' + tunnelId
25+
}, callback);
26+
},
27+
1828
deleteTunnel (tunnelId, callback) {
1929
if (!tunnelId) {
2030
throw new Error('Tunnel ID is required');
@@ -23,5 +33,13 @@ module.exports = {
2333
method: 'DELETE',
2434
url: '/tunnel/' + tunnelId
2535
}, callback);
36+
},
37+
38+
// Tear down whichever tunnel is currently active (no ID needed).
39+
deleteActiveTunnel (callback) {
40+
return this.request({
41+
method: 'DELETE',
42+
url: '/tunnel'
43+
}, callback);
2644
}
2745
};

lib/resources/user.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ module.exports = {
88
}, callback);
99
},
1010

11+
// Returns the account's API key and secret: { key, secret }.
12+
getUserKeys (callback) {
13+
return this.request({
14+
method: 'GET',
15+
url: '/user/keys'
16+
}, callback);
17+
},
18+
1119
updateUserInfo (data, callback) {
1220
if (!data) {
1321
throw new Error('Data is required');

test/async-await.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ const TbApi = require('../lib/api.js');
44
const assert = require('assert');
55

66
describe('TestingBot API Async/Await Tests', function() {
7+
// Creating a real cloud session can take well over the default timeout.
8+
this.timeout(30000);
9+
710
beforeEach(function() {
811
this.api = new TbApi();
912
});

test/cli.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,12 +305,12 @@ describe('TestingBot CLI Tests', function () {
305305
assert(Array.isArray(data));
306306
});
307307

308-
it('should list web browsers', async function () {
308+
it('should list webdriver browsers', async function () {
309309
if (!process.env.TB_KEY || !process.env.TB_SECRET) {
310310
this.skip();
311311
}
312312

313-
const { stdout } = await execWithEnv(`${binaryPath} browsers list web`, {
313+
const { stdout } = await execWithEnv(`${binaryPath} browsers list webdriver`, {
314314
TB_KEY: process.env.TB_KEY,
315315
TB_SECRET: process.env.TB_SECRET
316316
});

test/request-shaping.test.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const querystring = require('querystring');
5+
const { newClient } = require('./support/client');
6+
7+
// Capture the final axios request options a method builds, with NO network:
8+
// stub _send (the single point every method funnels through) and inspect what
9+
// it was handed. These tests lock in the correctness fixes from the audit.
10+
function capture (invoke) {
11+
const api = newClient({ api_key: 'k', api_secret: 's' });
12+
let captured = null;
13+
api._send = function (requestOptions, callback) {
14+
captured = requestOptions;
15+
if (callback) callback(null, {});
16+
return Promise.resolve({});
17+
};
18+
invoke(api);
19+
return captured;
20+
}
21+
22+
describe('Request shaping', function () {
23+
describe('audit fixes', function () {
24+
it('getTests sends count (not limit) as the page size', function () {
25+
const r = capture(api => api.getTests(0, 25));
26+
assert.strictEqual(r.method, 'GET');
27+
assert.match(r.url, /\/v1\/tests\/$/);
28+
assert.deepStrictEqual(r.params, { offset: 0, count: 25 });
29+
assert.ok(!('limit' in r.params), 'should not send a limit param');
30+
});
31+
32+
it('getDevice puts the id in the path, not a query param', function () {
33+
const r = capture(api => api.getDevice('A123'));
34+
assert.strictEqual(r.url, 'https://api.testingbot.com/v1/devices/A123');
35+
assert.ok(!r.params, 'should not send a query param');
36+
});
37+
38+
it('takeScreenshot uses snake_case option names', function () {
39+
const r = capture(api =>
40+
api.takeScreenshot('https://x', [{ browserName: 'chrome' }], '1280x1024', 5, true, 'https://cb')
41+
);
42+
assert.strictEqual(r.headers['Content-Type'], 'application/json');
43+
assert.strictEqual(r.data.wait_time, 5);
44+
assert.strictEqual(r.data.fullpage, true);
45+
assert.strictEqual(r.data.callback_url, 'https://cb');
46+
assert.ok(!('waitTime' in r.data) && !('fullPage' in r.data) && !('callbackURL' in r.data));
47+
});
48+
49+
it('createUserInTeam sends flat top-level fields (no user[...] wrapper)', function () {
50+
const r = capture(api => api.createUserInTeam({ email: 'a@b.com', password: 'pw' }));
51+
const parsed = querystring.parse(r.data);
52+
assert.strictEqual(parsed.email, 'a@b.com');
53+
assert.strictEqual(parsed.password, 'pw');
54+
assert.ok(!r.data.includes('user'), 'should not nest under user[...]');
55+
});
56+
57+
it('updateUserInfo wraps the body under user[...]', function () {
58+
const r = capture(api => api.updateUserInfo({ first_name: 'Jo' }));
59+
const parsed = querystring.parse(r.data);
60+
assert.strictEqual(parsed['user[first_name]'], 'Jo');
61+
});
62+
63+
it('getStorageFile strips the tb:// prefix and encodes the appkey', function () {
64+
const r = capture(api => api.getStorageFile('tb://abc123'));
65+
assert.strictEqual(r.url, 'https://api.testingbot.com/v1/storage/abc123');
66+
});
67+
68+
it('createSession merges caller capabilities over the defaults', function () {
69+
const r = capture(api => api.createSession({ capabilities: { browserName: 'firefox' } }));
70+
assert.strictEqual(r.url, 'https://cloud.testingbot.com/session');
71+
assert.strictEqual(r.data.capabilities.browserName, 'firefox');
72+
assert.strictEqual(r.data.capabilities.browserVersion, 'latest');
73+
assert.strictEqual(r.data.capabilities.platform, 'WIN10');
74+
});
75+
});
76+
77+
describe('Tier-1 methods', function () {
78+
it('getIpRanges -> GET /v1/configuration/ip-ranges', function () {
79+
const r = capture(api => api.getIpRanges());
80+
assert.strictEqual(r.method, 'GET');
81+
assert.match(r.url, /\/v1\/configuration\/ip-ranges$/);
82+
});
83+
84+
it('getTunnelById -> GET /v1/tunnel/:id', function () {
85+
const r = capture(api => api.getTunnelById(42));
86+
assert.strictEqual(r.method, 'GET');
87+
assert.strictEqual(r.url, 'https://api.testingbot.com/v1/tunnel/42');
88+
});
89+
90+
it('deleteActiveTunnel -> DELETE /v1/tunnel', function () {
91+
const r = capture(api => api.deleteActiveTunnel());
92+
assert.strictEqual(r.method, 'DELETE');
93+
assert.strictEqual(r.url, 'https://api.testingbot.com/v1/tunnel');
94+
});
95+
96+
it('getUserClientKey -> GET /v1/team-management/users/:id/client-key', function () {
97+
const r = capture(api => api.getUserClientKey(7));
98+
assert.strictEqual(r.url, 'https://api.testingbot.com/v1/team-management/users/7/client-key');
99+
});
100+
101+
it('getUserKeys -> GET /v1/user/keys', function () {
102+
const r = capture(api => api.getUserKeys());
103+
assert.match(r.url, /\/v1\/user\/keys$/);
104+
});
105+
});
106+
});

0 commit comments

Comments
 (0)