@insignia-education/api-sdk-js 0.15.61 → 0.15.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm-publish-github-packages.yml +95 -12
- package/coverage/clover.xml +132 -1512
- package/coverage/coverage-final.json +1 -47
- package/coverage/lcov-report/Client.js.html +463 -0
- package/coverage/lcov-report/index.html +20 -65
- package/coverage/lcov-report/src/Client.js.html +141 -132
- package/coverage/lcov-report/src/api/index.html +1 -1
- package/coverage/lcov-report/src/api/index.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/Accounts.js.html +3 -3
- package/coverage/lcov-report/src/api/v1/Admin.js.html +3 -3
- package/coverage/lcov-report/src/api/v1/Auth.js.html +7 -7
- package/coverage/lcov-report/src/api/v1/Categories.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/Changelogs.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/Configs.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/ContactForms.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/ConversationalTopics.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/Countries.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/Coupons.js.html +16 -16
- package/coverage/lcov-report/src/api/v1/Courses.js.html +27 -27
- package/coverage/lcov-report/src/api/v1/Currencies.js.html +10 -10
- package/coverage/lcov-report/src/api/v1/Dashboard.js.html +3 -3
- package/coverage/lcov-report/src/api/v1/EducationCenter.js.html +3 -3
- package/coverage/lcov-report/src/api/v1/Employee.js.html +5 -5
- package/coverage/lcov-report/src/api/v1/Files.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/Forums.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/Hashes.js.html +16 -13
- package/coverage/lcov-report/src/api/v1/Insignias.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/Languages.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/MailBlacklist.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/MailingLists.js.html +3 -3
- package/coverage/lcov-report/src/api/v1/Offers.js.html +16 -16
- package/coverage/lcov-report/src/api/v1/Organizations.js.html +52 -52
- package/coverage/lcov-report/src/api/v1/PaymentMethods.js.html +7 -7
- package/coverage/lcov-report/src/api/v1/Payments.js.html +3 -3
- package/coverage/lcov-report/src/api/v1/Premiums.js.html +3 -3
- package/coverage/lcov-report/src/api/v1/Quizzes.js.html +42 -42
- package/coverage/lcov-report/src/api/v1/Sales.js.html +3 -3
- package/coverage/lcov-report/src/api/v1/Search.js.html +3 -3
- package/coverage/lcov-report/src/api/v1/ShortLinks.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/Surveys.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/Taxes.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/Teacher.js.html +100 -70
- package/coverage/lcov-report/src/api/v1/Telegram.js.html +3 -3
- package/coverage/lcov-report/src/api/v1/TwoFactor.js.html +3 -3
- package/coverage/lcov-report/src/api/v1/UserSessionTypes.js.html +3 -3
- package/coverage/lcov-report/src/api/v1/UserTypes.js.html +8 -8
- package/coverage/lcov-report/src/api/v1/Users.js.html +112 -16
- package/coverage/lcov-report/src/api/v1/Utm.js.html +3 -3
- package/coverage/lcov-report/src/api/v1/Webauthn.js.html +3 -3
- package/coverage/lcov-report/src/api/v1/Zoom.js.html +19 -19
- package/coverage/lcov-report/src/api/v1/index.html +154 -184
- package/coverage/lcov-report/src/api/v1/index.js.html +52 -64
- package/coverage/lcov-report/src/index.html +21 -21
- package/coverage/lcov-report/src/index.js.html +3 -3
- package/coverage/lcov-report/tests/helpers.js.html +11 -11
- package/coverage/lcov-report/tests/index.html +7 -7
- package/coverage/lcov.info +146 -2451
- package/coverage/test-report.html +1 -10
- package/jest.config.mjs +12 -1
- package/package.json +2 -2
- package/src/api/v1/Coupons.js +11 -0
- package/src/api/v1/Courses.js +2 -0
- package/src/api/v1/Hashes.js +3 -2
- package/src/api/v1/Telegram.js +6 -0
- package/src/api/v1/Users.js +12 -0
- package/src/api/v1/index.js +0 -4
- package/tests/integration/api/v1/accounts.test.js +5 -11
- package/tests/integration/api/v1/auth/login.test.js +18 -34
- package/tests/integration/api/v1/conversational-topics.test.js +3 -5
- package/tests/integration/api/v1/countries.test.js +1 -1
- package/tests/integration/api/v1/coupons.test.js +2 -9
- package/tests/integration/api/v1/currencies/index.test.js +1 -1
- package/tests/integration/api/v1/files.test.js +18 -15
- package/tests/integration/api/v1/hashes.test.js +2 -10
- package/tests/integration/api/v1/mail-blacklist.test.js +2 -9
- package/tests/integration/api/v1/offers.test.js +2 -9
- package/tests/integration/api/v1/organizations.test.js +8 -8
- package/tests/integration/api/v1/quizzes/index.test.js +0 -2
- package/tests/integration/api/v1/short-links.test.js +2 -9
- package/tests/integration/api/v1/taxes.test.js +2 -3
- package/tests/integration/api/v1/teacher/index.test.js +1 -1
- package/tests/integration/api/v1/telegram.test.js +33 -0
- package/tests/integration/api/v1/user-types.test.js +2 -4
- package/tests/integration/api/v1/users/index.test.js +10 -13
- package/tests/integration/api/v1/users/telegram.test.js +36 -0
- package/tests/integration/api/v1/zoom.test.js +2 -9
- package/src/api/v1/Categories.js +0 -12
- package/src/api/v1/Configs.js +0 -12
- package/tests/integration/api/v1/categories.test.js +0 -19
- package/tests/integration/api/v1/configs.test.js +0 -20
|
@@ -274,13 +274,4 @@ header {
|
|
|
274
274
|
font-size: 1rem;
|
|
275
275
|
padding: 0 0.5rem;
|
|
276
276
|
}
|
|
277
|
-
</style></head><body><main class="jesthtml-content"><header><h1 id="title">API SDK Test Report</h1></header><section id="metadata-container"><div id="timestamp">Started: 2026-08-
|
|
278
|
-
at InsigniaApiV1.#request (/Users/roquendo/code/insignia-education/api-sdk-js/src/Client.js:82:25)
|
|
279
|
-
at processTicksAndRejections (node:internal/process/task_queues:105:5)
|
|
280
|
-
at Object.<anonymous> (/Users/roquendo/code/insignia-education/api-sdk-js/tests/integration/api/v1/accounts.test.js:9:9)</pre></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">api/v1/accounts</div><div class="test-title">get | authenticated but other type</div><div class="test-status">failed</div><div class="test-duration">0.648s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: HTTP 422
|
|
281
|
-
at InsigniaApiV1.#request (/Users/roquendo/code/insignia-education/api-sdk-js/src/Client.js:82:25)
|
|
282
|
-
at processTicksAndRejections (node:internal/process/task_queues:105:5)
|
|
283
|
-
at Object.<anonymous> (/Users/roquendo/code/insignia-education/api-sdk-js/tests/integration/api/v1/accounts.test.js:16:9)</pre></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">api/v1/accounts</div><div class="test-title">get | authenticated</div><div class="test-status">failed</div><div class="test-duration">0.802s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: HTTP 422
|
|
284
|
-
at InsigniaApiV1.#request (/Users/roquendo/code/insignia-education/api-sdk-js/src/Client.js:82:25)
|
|
285
|
-
at processTicksAndRejections (node:internal/process/task_queues:105:5)
|
|
286
|
-
at Object.<anonymous> (/Users/roquendo/code/insignia-education/api-sdk-js/tests/integration/api/v1/accounts.test.js:25:9)</pre></div></div></div></details></main></body></html>
|
|
277
|
+
</style></head><body><main class="jesthtml-content"><header><h1 id="title">API SDK Test Report</h1></header><section id="metadata-container"><div id="timestamp">Started: 2026-08-10 12:18:48</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed ">1 passed</div><div class="summary-failed summary-empty">0 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (22)</div><div class="summary-passed ">22 passed</div><div class="summary-failed summary-empty">0 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></section><details id="suite-1" class="suite-container" open=""><summary class="suite-info"><div class="suite-path">/Users/roquendo/code/insignia-education/api-sdk-js/tests/client.test.js</div><div class="suite-time">0.558s</div></summary><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">baseUrl resolution</div><div class="test-title">uses explicit baseUrl when provided</div><div class="test-status">passed</div><div class="test-duration">0.003s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">baseUrl resolution</div><div class="test-title">strips trailing slash from explicit baseUrl</div><div class="test-status">passed</div><div class="test-duration">0.004s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">baseUrl resolution</div><div class="test-title">uses INSIGNIA_EDUCATION_API_BASE_URL env var when baseUrl is null</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">baseUrl resolution</div><div class="test-title">falls back to https://insigniaeducation.com when baseUrl is null and env var is not set</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">baseUrl resolution</div><div class="test-title">falls back to default when constructed with no arguments</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">baseUrl resolution</div><div class="test-title">explicit baseUrl takes precedence over env var</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">baseUrl resolution</div><div class="test-title">falls back to default when process is undefined</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">HTTP methods</div><div class="test-title">get() sends GET with no body</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">HTTP methods</div><div class="test-title">post() sends POST with body</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">HTTP methods</div><div class="test-title">post() sends POST with no body when not provided</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">HTTP methods</div><div class="test-title">put() sends PUT with body</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">HTTP methods</div><div class="test-title">put() sends PUT with no body when not provided</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">HTTP methods</div><div class="test-title">patch() sends PATCH with body</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">HTTP methods</div><div class="test-title">patch() sends PATCH with no body when not provided</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">HTTP methods</div><div class="test-title">del() sends DELETE with no body</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">HTTP methods</div><div class="test-title">JSON headers are always sent</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">HTTP methods</div><div class="test-title">credentials is always include</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">HTTP methods</div><div class="test-title">stores response cookies and sends them on later requests in Node</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">HTTP methods</div><div class="test-title">returns parsed JSON from response</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">HTTP methods</div><div class="test-title">returns null for empty response text</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">HTTP methods</div><div class="test-title">returns null for no-content responses</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">HTTP methods</div><div class="test-title">returns null when json parsing fails because the response is empty</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div></div></details></main></body></html>
|
package/jest.config.mjs
CHANGED
|
@@ -14,6 +14,14 @@ const config = {
|
|
|
14
14
|
}],
|
|
15
15
|
],
|
|
16
16
|
|
|
17
|
+
// Run via `npm test`, which invokes unit and integration as two separate
|
|
18
|
+
// `jest --selectProjects` calls (integration also with --runInBand)
|
|
19
|
+
// rather than one combined parallel run: Jest has a multi-project quirk
|
|
20
|
+
// where a later project's tests can silently fall back to the global
|
|
21
|
+
// 5000ms default instead of this project's own testTimeout when both
|
|
22
|
+
// projects run together in one process, and integration tests hit a
|
|
23
|
+
// single-threaded `php artisan serve` backend that serializes requests
|
|
24
|
+
// anyway — parallel workers just queue up and time out against it.
|
|
17
25
|
projects: [
|
|
18
26
|
{
|
|
19
27
|
displayName: 'unit',
|
|
@@ -28,7 +36,10 @@ const config = {
|
|
|
28
36
|
testMatch: ['<rootDir>/tests/integration/**/*.test.js'],
|
|
29
37
|
transform: { '^.+\\.(js|jsx)$': 'babel-jest' },
|
|
30
38
|
setupFiles: ['<rootDir>/tests/integration/loadEnv.js'],
|
|
31
|
-
|
|
39
|
+
// Generous — amd64 emulation (api-sdk-js's Docker test env on
|
|
40
|
+
// Apple Silicon) occasionally makes an individual request take
|
|
41
|
+
// 10-20s+ under CPU contention.
|
|
42
|
+
testTimeout: 60000,
|
|
32
43
|
},
|
|
33
44
|
],
|
|
34
45
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@insignia-education/api-sdk-js",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.63",
|
|
4
4
|
"description": "JavaScript SDK for the Insignia Education API",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"./api/v1": "./src/api/v1/index.js"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"test": "jest",
|
|
13
|
+
"test": "jest --selectProjects unit && jest --selectProjects integration --runInBand --testTimeout=60000",
|
|
14
14
|
"lint": "eslint src/"
|
|
15
15
|
},
|
|
16
16
|
"repository": {
|
package/src/api/v1/Coupons.js
CHANGED
|
@@ -16,4 +16,15 @@ export default class Coupons {
|
|
|
16
16
|
|
|
17
17
|
/** Employee/sales-only: usage stats + full redemption history for one coupon. */
|
|
18
18
|
stats(id) { return this.#client.get(`/coupons/${id}/stats`); }
|
|
19
|
+
|
|
20
|
+
/** Users explicitly granted access to redeem this coupon. */
|
|
21
|
+
users(id) {
|
|
22
|
+
const base = `/coupons/${id}/users`;
|
|
23
|
+
const client = this.#client;
|
|
24
|
+
return {
|
|
25
|
+
get: () => client.get(base),
|
|
26
|
+
add: ({ email, reason }) => client.put(base, { email, reason }),
|
|
27
|
+
remove: (userId) => client.del(`${base}/${userId}`),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
19
30
|
}
|
package/src/api/v1/Courses.js
CHANGED
|
@@ -45,6 +45,8 @@ export default class Courses {
|
|
|
45
45
|
syncTelegramToUsers: (id) => this.#client.post(`${base}/${id}/sync-user-courses`),
|
|
46
46
|
/** Reconciles this date's sessions with its current teacher/config set: creates missing ones, reassigns the teacher on existing ones, deletes untouched ones that no longer match the schedule. Safe to re-run any time the config, date range or teacher changes. */
|
|
47
47
|
syncSessions: (id) => this.#client.post(`${base}/${id}/sync-sessions`),
|
|
48
|
+
/** Reconciles per-student attendance rows for this date's generated sessions against who's currently enrolled (UserCourse). Safe to re-run any time the roster changes. */
|
|
49
|
+
syncAttendances: (id) => this.#client.post(`${base}/${id}/sync-attendances`),
|
|
48
50
|
/** Sessions already generated for this date. */
|
|
49
51
|
sessions: (id) => this.#client.get(`${base}/${id}/sessions`),
|
|
50
52
|
/** Create a Zoom meeting for one of this date's generated sessions. */
|
package/src/api/v1/Hashes.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Read-only — hashes (password-reset/verify/phantom-login tokens etc.) are
|
|
2
|
+
// created and deleted exclusively by their owning flows (forgot-password,
|
|
3
|
+
// email-verify, admin phantom-login), never through a generic public API.
|
|
1
4
|
export default class Hashes {
|
|
2
5
|
#client;
|
|
3
6
|
|
|
@@ -6,6 +9,4 @@ export default class Hashes {
|
|
|
6
9
|
}
|
|
7
10
|
|
|
8
11
|
get(id = null) { return id ? this.#client.get(`/hashes/${id}`) : this.#client.get('/hashes'); }
|
|
9
|
-
generate(data) { return this.#client.put('/hashes/generate', data); }
|
|
10
|
-
delete(id) { return this.#client.del(`/hashes/${id}`); }
|
|
11
12
|
}
|
package/src/api/v1/Telegram.js
CHANGED
|
@@ -10,4 +10,10 @@ export default class Telegram {
|
|
|
10
10
|
|
|
11
11
|
/** Admin-only: starts (or continues) a QR-code login. */
|
|
12
12
|
qrLogin() { return this.#client.post('/telegram/qr-login'); }
|
|
13
|
+
|
|
14
|
+
/** Admin-only: current webhook registration for the main bot (account linking + notifications). */
|
|
15
|
+
webhookStatus() { return this.#client.get('/telegram/webhook'); }
|
|
16
|
+
|
|
17
|
+
/** Admin-only: (re)registers this API's /webhooks/telegram endpoint with Telegram. Idempotent. */
|
|
18
|
+
setWebhook() { return this.#client.post('/telegram/webhook'); }
|
|
13
19
|
}
|
package/src/api/v1/Users.js
CHANGED
|
@@ -202,4 +202,16 @@ export default class Users {
|
|
|
202
202
|
reject: () => client.post(`${base}/reject`),
|
|
203
203
|
};
|
|
204
204
|
}
|
|
205
|
+
|
|
206
|
+
/** Verified Telegram account linking for this user. Owner or staff only. */
|
|
207
|
+
telegram(userId) {
|
|
208
|
+
const base = `/users/${userId}/telegram`;
|
|
209
|
+
const client = this.#client;
|
|
210
|
+
return {
|
|
211
|
+
/** Mints a { hash, link, expires_at } deep link — tapping it in Telegram links this user's chat_id via the bot webhook. */
|
|
212
|
+
linkToken: () => client.post(`${base}/link-token`),
|
|
213
|
+
/** Fallback flow: completes a link the bot proposed after an organic (no-payload) /start, given the { chat_id, username, expires_at, signature } it replied with. */
|
|
214
|
+
connect: (data) => client.post(`${base}/connect`, data),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
205
217
|
}
|
package/src/api/v1/index.js
CHANGED
|
@@ -2,9 +2,7 @@ import InsigniaApi from '../index.js';
|
|
|
2
2
|
import Admin from './Admin.js';
|
|
3
3
|
import Auth from './Auth.js';
|
|
4
4
|
import Accounts from './Accounts.js';
|
|
5
|
-
import Categories from './Categories.js';
|
|
6
5
|
import Changelogs from './Changelogs.js';
|
|
7
|
-
import Configs from './Configs.js';
|
|
8
6
|
import ContactForms from './ContactForms.js';
|
|
9
7
|
import ConversationalTopics from './ConversationalTopics.js';
|
|
10
8
|
import Countries from './Countries.js';
|
|
@@ -57,9 +55,7 @@ export default class InsigniaApiV1 extends InsigniaApi {
|
|
|
57
55
|
this.admin = new Admin(this);
|
|
58
56
|
this.auth = new Auth(this);
|
|
59
57
|
this.accounts = new Accounts(this);
|
|
60
|
-
this.categories = new Categories(this);
|
|
61
58
|
this.changelogs = new Changelogs(this);
|
|
62
|
-
this.configs = new Configs(this);
|
|
63
59
|
this.contactForms = new ContactForms(this);
|
|
64
60
|
this.conversationalTopics = new ConversationalTopics(this);
|
|
65
61
|
this.countries = new Countries(this);
|
|
@@ -5,21 +5,15 @@ import {
|
|
|
5
5
|
} from '../../../helpers.js';
|
|
6
6
|
|
|
7
7
|
describe('api/v1/accounts', () => {
|
|
8
|
+
// Client#request() throws on non-2xx (err.status, err.data) rather than
|
|
9
|
+
// resolving — assert against a rejected promise, not a resolved
|
|
10
|
+
// {success, status}.
|
|
8
11
|
test('get | unauthenticated', async () => {
|
|
9
|
-
await api.accounts.get()
|
|
10
|
-
.then(response => {
|
|
11
|
-
expect(response.success).toBe(false);
|
|
12
|
-
expect(response.status).toBe(401);
|
|
13
|
-
});
|
|
12
|
+
await expect(api.accounts.get()).rejects.toMatchObject({ status: 401 });
|
|
14
13
|
});
|
|
15
14
|
test('get | authenticated but other type', async () => {
|
|
16
15
|
await loginCustomer();
|
|
17
|
-
await api.accounts.get()
|
|
18
|
-
.then(response => {
|
|
19
|
-
console.log(response);
|
|
20
|
-
expect(response.success).toBe(false);
|
|
21
|
-
expect(response.status).toBe(403);
|
|
22
|
-
});
|
|
16
|
+
await expect(api.accounts.get()).rejects.toMatchObject({ status: 403 });
|
|
23
17
|
});
|
|
24
18
|
test('get | authenticated', async () => {
|
|
25
19
|
await loginAdmin();
|
|
@@ -1,56 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
api,
|
|
3
|
-
loginCustomer,
|
|
1
|
+
import {
|
|
2
|
+
api,
|
|
4
3
|
loginAdmin,
|
|
5
|
-
logout,
|
|
6
4
|
} from '../../../../helpers.js';
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
6
|
describe('api/v1/auth/login', () => {
|
|
7
|
+
// Client#request() throws on non-2xx (err.status, err.data) rather than
|
|
8
|
+
// resolving — assert against a rejected promise, not a resolved
|
|
9
|
+
// {success, status, errors}. Also no logout() before these — a fresh
|
|
10
|
+
// `api` client (one per test file) starts out unauthenticated, and
|
|
11
|
+
// logout() itself throws 401 when there's no session to clear.
|
|
12
12
|
test('POST | failure | no email validation', async () => {
|
|
13
13
|
const password = process.env.TEST_PASSWORD;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
.
|
|
17
|
-
|
|
18
|
-
expect(response["success"]).toBeDefined();
|
|
19
|
-
expect(response["status"]).toBeDefined();
|
|
20
|
-
expect(response["status"]).toBe(422);
|
|
21
|
-
expect(response["errors"]).toBeDefined();
|
|
22
|
-
expect(Object.values(response["errors"]).length > 0).toBe(true);
|
|
23
|
-
expect(response["errors"]["email"]).toBeDefined();
|
|
24
|
-
}));
|
|
14
|
+
await expect(api.auth.login({ password })).rejects.toMatchObject({
|
|
15
|
+
status: 422,
|
|
16
|
+
data: { errors: expect.objectContaining({ email: expect.anything() }) },
|
|
17
|
+
});
|
|
25
18
|
});
|
|
26
19
|
|
|
27
20
|
test('POST | failure | no password validation', async () => {
|
|
28
21
|
const email = process.env.TEST_EMAIL;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.
|
|
32
|
-
|
|
33
|
-
expect(response["success"]).toBeDefined();
|
|
34
|
-
expect(response["errors"]).toBeDefined();
|
|
35
|
-
expect(response["status"]).toBeDefined();
|
|
36
|
-
expect(response["status"]).toBe(422);
|
|
37
|
-
expect(Object.values(response["errors"]).length > 0).toBe(true);
|
|
38
|
-
expect(response["errors"]["password"]).toBeDefined();
|
|
39
|
-
}));
|
|
40
|
-
|
|
22
|
+
await expect(api.auth.login({ email })).rejects.toMatchObject({
|
|
23
|
+
status: 422,
|
|
24
|
+
data: { errors: expect.objectContaining({ password: expect.anything() }) },
|
|
25
|
+
});
|
|
41
26
|
});
|
|
42
27
|
|
|
43
28
|
test('POST | success', async () => {
|
|
44
|
-
|
|
45
|
-
.then(() => loginAdmin()
|
|
29
|
+
await loginAdmin()
|
|
46
30
|
.then(response => {
|
|
47
31
|
expect(response["success"]).toBeDefined();
|
|
48
32
|
expect(response["success"]).toBe("ok");
|
|
49
|
-
})
|
|
33
|
+
});
|
|
50
34
|
});
|
|
51
35
|
|
|
52
36
|
test('POST | double login', async () => {
|
|
53
|
-
loginAdmin()
|
|
37
|
+
await loginAdmin()
|
|
54
38
|
.then(() => loginAdmin()
|
|
55
39
|
.then(response => {
|
|
56
40
|
expect(response["success"]).toBeDefined();
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
const api = new InsigniaApiV1(process.env.INSIGNIA_EDUCATION_API_BASE_URL);
|
|
1
|
+
import { api, loginAdmin } from '../../../helpers.js';
|
|
4
2
|
|
|
5
3
|
describe('api/v1/conversational-topics', () => {
|
|
6
4
|
test('get', async () => {
|
|
5
|
+
await loginAdmin();
|
|
7
6
|
await api.conversationalTopics.get()
|
|
8
7
|
.then(response => {
|
|
9
8
|
response = Object.values(response);
|
|
10
9
|
expect(response.length > 0).toBe(true);
|
|
11
10
|
response.forEach(topic => {
|
|
12
11
|
expect(topic["id"]).toBeDefined();
|
|
13
|
-
expect(topic["cod"]).toBeDefined();
|
|
14
12
|
expect(topic["title"]).toBeDefined();
|
|
15
|
-
expect(topic["
|
|
13
|
+
expect(topic["date"]).toBeDefined();
|
|
16
14
|
});
|
|
17
15
|
});
|
|
18
16
|
});
|
|
@@ -5,7 +5,7 @@ const api = new InsigniaApiV1(process.env.INSIGNIA_EDUCATION_API_BASE_URL);
|
|
|
5
5
|
|
|
6
6
|
describe('api/v1/countries', () => {
|
|
7
7
|
test('get', async () => {
|
|
8
|
-
api.countries.get()
|
|
8
|
+
await api.countries.get()
|
|
9
9
|
.then(response => {
|
|
10
10
|
response = Object.values(response);
|
|
11
11
|
expect(response.length > 0).toBe(true);
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
const api = new InsigniaApiV1(process.env.INSIGNIA_EDUCATION_API_BASE_URL);
|
|
4
|
-
|
|
5
|
-
const login = () => api.auth.login({
|
|
6
|
-
email: process.env.TEST_EMAIL,
|
|
7
|
-
password: process.env.TEST_PASSWORD,
|
|
8
|
-
});
|
|
1
|
+
import { api, loginAdmin } from '../../../helpers.js';
|
|
9
2
|
|
|
10
3
|
describe('api/v1/coupons', () => {
|
|
11
4
|
test('get | authenticated', async () => {
|
|
12
|
-
await
|
|
5
|
+
await loginAdmin();
|
|
13
6
|
await api.coupons.get()
|
|
14
7
|
.then(response => {
|
|
15
8
|
response = Object.values(response);
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import { Blob } from 'buffer';
|
|
2
|
-
import
|
|
2
|
+
import { api, loginAdmin } from '../../../helpers.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
// A real 1x1 transparent PNG (67 bytes) — the upload endpoint sniffs actual
|
|
5
|
+
// file content, not just the claimed Content-Type, so arbitrary text with an
|
|
6
|
+
// 'image/png' MIME hint gets rejected with "Unsupported file type".
|
|
7
|
+
const PNG_1X1 = Buffer.from(
|
|
8
|
+
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=',
|
|
9
|
+
'base64'
|
|
10
|
+
);
|
|
10
11
|
|
|
11
12
|
describe('api/v1/files', () => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// list() (paginated), not get() — get(id) requires an id and hits
|
|
14
|
+
// /files/{id}, unlike most other resources' get(id = null).
|
|
15
|
+
test('list | authenticated', async () => {
|
|
16
|
+
await loginAdmin();
|
|
17
|
+
await api.files.list()
|
|
15
18
|
.then(response => {
|
|
16
|
-
|
|
17
|
-
expect(Array.isArray(
|
|
18
|
-
|
|
19
|
+
const files = response["data"];
|
|
20
|
+
expect(Array.isArray(files)).toBe(true);
|
|
21
|
+
files.forEach(file => {
|
|
19
22
|
expect(file["id"]).toBeDefined();
|
|
20
23
|
expect(file["created_at"]).toBeDefined();
|
|
21
24
|
expect(file["updated_at"]).toBeDefined();
|
|
@@ -24,8 +27,8 @@ describe('api/v1/files', () => {
|
|
|
24
27
|
});
|
|
25
28
|
|
|
26
29
|
test('upload | authenticated', async () => {
|
|
27
|
-
await
|
|
28
|
-
const blob = new Blob([
|
|
30
|
+
await loginAdmin();
|
|
31
|
+
const blob = new Blob([PNG_1X1], { type: 'image/png' });
|
|
29
32
|
const fd = new FormData();
|
|
30
33
|
fd.append('file', blob, 'test.png');
|
|
31
34
|
fd.append('type', 'uploads');
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
const api = new InsigniaApiV1(process.env.INSIGNIA_EDUCATION_API_BASE_URL);
|
|
4
|
-
|
|
5
|
-
const login = () => api.auth.login({
|
|
6
|
-
email: process.env.TEST_EMAIL,
|
|
7
|
-
password: process.env.TEST_PASSWORD,
|
|
8
|
-
});
|
|
1
|
+
import { api, loginAdmin } from '../../../helpers.js';
|
|
9
2
|
|
|
10
3
|
describe('api/v1/hashes', () => {
|
|
11
4
|
test('get | authenticated', async () => {
|
|
12
|
-
await
|
|
5
|
+
await loginAdmin();
|
|
13
6
|
await api.hashes.get()
|
|
14
7
|
.then(response => {
|
|
15
8
|
response = Object.values(response);
|
|
@@ -17,7 +10,6 @@ describe('api/v1/hashes', () => {
|
|
|
17
10
|
response.forEach(hash => {
|
|
18
11
|
expect(hash["id"]).toBeDefined();
|
|
19
12
|
expect(hash["hash"]).toBeDefined();
|
|
20
|
-
expect(hash["purpose_id"]).toBeDefined();
|
|
21
13
|
expect(hash["created_at"]).toBeDefined();
|
|
22
14
|
expect(hash["updated_at"]).toBeDefined();
|
|
23
15
|
});
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
const api = new InsigniaApiV1(process.env.INSIGNIA_EDUCATION_API_BASE_URL);
|
|
4
|
-
|
|
5
|
-
const login = () => api.auth.login({
|
|
6
|
-
email: process.env.TEST_EMAIL,
|
|
7
|
-
password: process.env.TEST_PASSWORD,
|
|
8
|
-
});
|
|
1
|
+
import { api, loginAdmin } from '../../../helpers.js';
|
|
9
2
|
|
|
10
3
|
describe('api/v1/mail-blacklist', () => {
|
|
11
4
|
test('get | authenticated', async () => {
|
|
12
|
-
await
|
|
5
|
+
await loginAdmin();
|
|
13
6
|
await api.mailBlacklist.get()
|
|
14
7
|
.then(response => {
|
|
15
8
|
response = Object.values(response);
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
const api = new InsigniaApiV1(process.env.INSIGNIA_EDUCATION_API_BASE_URL);
|
|
4
|
-
|
|
5
|
-
const login = () => api.auth.login({
|
|
6
|
-
email: process.env.TEST_EMAIL,
|
|
7
|
-
password: process.env.TEST_PASSWORD,
|
|
8
|
-
});
|
|
1
|
+
import { api, loginAdmin } from '../../../helpers.js';
|
|
9
2
|
|
|
10
3
|
describe('api/v1/offers', () => {
|
|
11
4
|
test('get | authenticated', async () => {
|
|
12
|
-
await
|
|
5
|
+
await loginAdmin();
|
|
13
6
|
await api.offers.get()
|
|
14
7
|
.then(response => {
|
|
15
8
|
response = Object.values(response);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
api,
|
|
3
|
-
|
|
3
|
+
loginAdmin,
|
|
4
4
|
logout,
|
|
5
5
|
} from '../../../helpers.js';
|
|
6
6
|
|
|
@@ -17,7 +17,7 @@ describe('api/v1/organizations', () => {
|
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
test('create | employee-and-above', async () => {
|
|
20
|
-
await
|
|
20
|
+
await loginAdmin();
|
|
21
21
|
const response = await api.organizations.create({ nice_name: 'SDK Test Org' });
|
|
22
22
|
expect(response["id"]).toBeDefined();
|
|
23
23
|
expect(response["nice_name"]).toBe('SDK Test Org');
|
|
@@ -25,7 +25,7 @@ describe('api/v1/organizations', () => {
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
test('get(id) | shape', async () => {
|
|
28
|
-
await
|
|
28
|
+
await loginAdmin();
|
|
29
29
|
const created = await api.organizations.create({ nice_name: 'SDK Test Org 2' });
|
|
30
30
|
const response = await api.organizations.get(created.id);
|
|
31
31
|
expect(response["id"]).toBe(created.id);
|
|
@@ -38,28 +38,28 @@ describe('api/v1/organizations', () => {
|
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
test('edit', async () => {
|
|
41
|
-
await
|
|
41
|
+
await loginAdmin();
|
|
42
42
|
const created = await api.organizations.create({ nice_name: 'SDK Test Org 3' });
|
|
43
43
|
const response = await api.organizations.edit(created.id, { nice_name: 'SDK Test Org 3 Edited' });
|
|
44
44
|
expect(response["nice_name"]).toBe('SDK Test Org 3 Edited');
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
test('members(id).get() | empty for a freshly created organization', async () => {
|
|
48
|
-
await
|
|
48
|
+
await loginAdmin();
|
|
49
49
|
const created = await api.organizations.create({ nice_name: 'SDK Test Org 4' });
|
|
50
50
|
const response = await api.organizations.members(created.id).get();
|
|
51
51
|
expect(Object.values(response)).toEqual([]);
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
test('members(id).courses(userId) | rejects a non-member user_id', async () => {
|
|
55
|
-
await
|
|
55
|
+
await loginAdmin();
|
|
56
56
|
const created = await api.organizations.create({ nice_name: 'SDK Test Org 5' });
|
|
57
57
|
await expect(api.organizations.members(created.id).courses(created.user_id))
|
|
58
58
|
.rejects.toMatchObject({ status: 422 });
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
test('members(id).createPayment(userId, data) | reaches validation, not the OwnerOrStaff 403', async () => {
|
|
62
|
-
await
|
|
62
|
+
await loginAdmin();
|
|
63
63
|
const created = await api.organizations.create({ nice_name: 'SDK Test Org 6' });
|
|
64
64
|
// Empty payload — asserts the route/method wiring bypasses OwnerOrStaff and lands
|
|
65
65
|
// in UserPaymentCreateRequest validation (422), never a 403.
|
|
@@ -77,7 +77,7 @@ describe('api/v1/users/organization-owners', () => {
|
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
test('get | employee-and-above', async () => {
|
|
80
|
-
await
|
|
80
|
+
await loginAdmin();
|
|
81
81
|
const response = Object.values(await api.users.organizationOwners());
|
|
82
82
|
expect(Array.isArray(response)).toBe(true);
|
|
83
83
|
response.forEach(user => {
|
|
@@ -12,8 +12,6 @@ describe('api/v1/quizzes', () => {
|
|
|
12
12
|
expect(Array.isArray(response)).toBe(true);
|
|
13
13
|
response.forEach(quiz => {
|
|
14
14
|
expect(quiz["id"]).toBeDefined();
|
|
15
|
-
expect(quiz["cod"]).toBeDefined();
|
|
16
|
-
expect(quiz["enabled"]).toBeDefined();
|
|
17
15
|
expect(quiz["created_at"]).toBeDefined();
|
|
18
16
|
expect(quiz["updated_at"]).toBeDefined();
|
|
19
17
|
});
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
const api = new InsigniaApiV1(process.env.INSIGNIA_EDUCATION_API_BASE_URL);
|
|
4
|
-
|
|
5
|
-
const login = () => api.auth.login({
|
|
6
|
-
email: process.env.TEST_EMAIL,
|
|
7
|
-
password: process.env.TEST_PASSWORD,
|
|
8
|
-
});
|
|
1
|
+
import { api, loginAdmin } from '../../../helpers.js';
|
|
9
2
|
|
|
10
3
|
describe('api/v1/short-links', () => {
|
|
11
4
|
test('get | authenticated', async () => {
|
|
12
|
-
await
|
|
5
|
+
await loginAdmin();
|
|
13
6
|
await api.shortLinks.get()
|
|
14
7
|
.then(response => {
|
|
15
8
|
response = Object.values(response);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
const api = new InsigniaApiV1(process.env.INSIGNIA_EDUCATION_API_BASE_URL);
|
|
1
|
+
import { api, loginAdmin } from '../../../helpers.js';
|
|
4
2
|
|
|
5
3
|
describe('api/v1/taxes', () => {
|
|
6
4
|
test('get', async () => {
|
|
5
|
+
await loginAdmin();
|
|
7
6
|
await api.taxes.get()
|
|
8
7
|
.then(response => {
|
|
9
8
|
response = Object.values(response);
|
|
@@ -38,7 +38,7 @@ describe('api/v1/teacher/config-sets', () => {
|
|
|
38
38
|
|
|
39
39
|
describe('api/v1/teacher/configs', () => {
|
|
40
40
|
test('get | authenticated', async () => {
|
|
41
|
-
await
|
|
41
|
+
await loginAdmin();
|
|
42
42
|
await api.teacher.configs().get()
|
|
43
43
|
.then(response => {
|
|
44
44
|
response = Object.values(response);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
api,
|
|
3
|
+
loginAdmin,
|
|
4
|
+
loginCustomer,
|
|
5
|
+
} from '../../../helpers.js';
|
|
6
|
+
|
|
7
|
+
describe('api/v1/telegram', () => {
|
|
8
|
+
test('webhookStatus | requires authentication', async () => {
|
|
9
|
+
await expect(api.telegram.webhookStatus()).rejects.toMatchObject({ status: 401 });
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('webhookStatus | forbidden below admin', async () => {
|
|
13
|
+
await loginCustomer();
|
|
14
|
+
await expect(api.telegram.webhookStatus()).rejects.toMatchObject({ status: 403 });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('webhookStatus | reachable by admin', async () => {
|
|
18
|
+
await loginAdmin();
|
|
19
|
+
await api.telegram.webhookStatus()
|
|
20
|
+
.then(response => {
|
|
21
|
+
expect(typeof response).toBe('object');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('setWebhook | requires authentication', async () => {
|
|
26
|
+
await expect(api.telegram.setWebhook()).rejects.toMatchObject({ status: 401 });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('setWebhook | forbidden below admin', async () => {
|
|
30
|
+
await loginCustomer();
|
|
31
|
+
await expect(api.telegram.setWebhook()).rejects.toMatchObject({ status: 403 });
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
const api = new InsigniaApiV1(process.env.INSIGNIA_EDUCATION_API_BASE_URL);
|
|
1
|
+
import { api, loginAdmin } from '../../../helpers.js';
|
|
4
2
|
|
|
5
3
|
describe('api/v1/user-types', () => {
|
|
6
4
|
test('get', async () => {
|
|
5
|
+
await loginAdmin();
|
|
7
6
|
await api.userTypes.get()
|
|
8
7
|
.then(response => {
|
|
9
8
|
response = Object.values(response);
|
|
@@ -12,7 +11,6 @@ describe('api/v1/user-types', () => {
|
|
|
12
11
|
expect(userType["id"]).toBeDefined();
|
|
13
12
|
expect(userType["cod"]).toBeDefined();
|
|
14
13
|
expect(userType["title"]).toBeDefined();
|
|
15
|
-
expect(userType["enabled"]).toBeDefined();
|
|
16
14
|
});
|
|
17
15
|
});
|
|
18
16
|
});
|