@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.
Files changed (90) hide show
  1. package/.github/workflows/npm-publish-github-packages.yml +95 -12
  2. package/coverage/clover.xml +132 -1512
  3. package/coverage/coverage-final.json +1 -47
  4. package/coverage/lcov-report/Client.js.html +463 -0
  5. package/coverage/lcov-report/index.html +20 -65
  6. package/coverage/lcov-report/src/Client.js.html +141 -132
  7. package/coverage/lcov-report/src/api/index.html +1 -1
  8. package/coverage/lcov-report/src/api/index.js.html +8 -8
  9. package/coverage/lcov-report/src/api/v1/Accounts.js.html +3 -3
  10. package/coverage/lcov-report/src/api/v1/Admin.js.html +3 -3
  11. package/coverage/lcov-report/src/api/v1/Auth.js.html +7 -7
  12. package/coverage/lcov-report/src/api/v1/Categories.js.html +8 -8
  13. package/coverage/lcov-report/src/api/v1/Changelogs.js.html +8 -8
  14. package/coverage/lcov-report/src/api/v1/Configs.js.html +8 -8
  15. package/coverage/lcov-report/src/api/v1/ContactForms.js.html +8 -8
  16. package/coverage/lcov-report/src/api/v1/ConversationalTopics.js.html +8 -8
  17. package/coverage/lcov-report/src/api/v1/Countries.js.html +8 -8
  18. package/coverage/lcov-report/src/api/v1/Coupons.js.html +16 -16
  19. package/coverage/lcov-report/src/api/v1/Courses.js.html +27 -27
  20. package/coverage/lcov-report/src/api/v1/Currencies.js.html +10 -10
  21. package/coverage/lcov-report/src/api/v1/Dashboard.js.html +3 -3
  22. package/coverage/lcov-report/src/api/v1/EducationCenter.js.html +3 -3
  23. package/coverage/lcov-report/src/api/v1/Employee.js.html +5 -5
  24. package/coverage/lcov-report/src/api/v1/Files.js.html +8 -8
  25. package/coverage/lcov-report/src/api/v1/Forums.js.html +8 -8
  26. package/coverage/lcov-report/src/api/v1/Hashes.js.html +16 -13
  27. package/coverage/lcov-report/src/api/v1/Insignias.js.html +8 -8
  28. package/coverage/lcov-report/src/api/v1/Languages.js.html +8 -8
  29. package/coverage/lcov-report/src/api/v1/MailBlacklist.js.html +8 -8
  30. package/coverage/lcov-report/src/api/v1/MailingLists.js.html +3 -3
  31. package/coverage/lcov-report/src/api/v1/Offers.js.html +16 -16
  32. package/coverage/lcov-report/src/api/v1/Organizations.js.html +52 -52
  33. package/coverage/lcov-report/src/api/v1/PaymentMethods.js.html +7 -7
  34. package/coverage/lcov-report/src/api/v1/Payments.js.html +3 -3
  35. package/coverage/lcov-report/src/api/v1/Premiums.js.html +3 -3
  36. package/coverage/lcov-report/src/api/v1/Quizzes.js.html +42 -42
  37. package/coverage/lcov-report/src/api/v1/Sales.js.html +3 -3
  38. package/coverage/lcov-report/src/api/v1/Search.js.html +3 -3
  39. package/coverage/lcov-report/src/api/v1/ShortLinks.js.html +8 -8
  40. package/coverage/lcov-report/src/api/v1/Surveys.js.html +8 -8
  41. package/coverage/lcov-report/src/api/v1/Taxes.js.html +8 -8
  42. package/coverage/lcov-report/src/api/v1/Teacher.js.html +100 -70
  43. package/coverage/lcov-report/src/api/v1/Telegram.js.html +3 -3
  44. package/coverage/lcov-report/src/api/v1/TwoFactor.js.html +3 -3
  45. package/coverage/lcov-report/src/api/v1/UserSessionTypes.js.html +3 -3
  46. package/coverage/lcov-report/src/api/v1/UserTypes.js.html +8 -8
  47. package/coverage/lcov-report/src/api/v1/Users.js.html +112 -16
  48. package/coverage/lcov-report/src/api/v1/Utm.js.html +3 -3
  49. package/coverage/lcov-report/src/api/v1/Webauthn.js.html +3 -3
  50. package/coverage/lcov-report/src/api/v1/Zoom.js.html +19 -19
  51. package/coverage/lcov-report/src/api/v1/index.html +154 -184
  52. package/coverage/lcov-report/src/api/v1/index.js.html +52 -64
  53. package/coverage/lcov-report/src/index.html +21 -21
  54. package/coverage/lcov-report/src/index.js.html +3 -3
  55. package/coverage/lcov-report/tests/helpers.js.html +11 -11
  56. package/coverage/lcov-report/tests/index.html +7 -7
  57. package/coverage/lcov.info +146 -2451
  58. package/coverage/test-report.html +1 -10
  59. package/jest.config.mjs +12 -1
  60. package/package.json +2 -2
  61. package/src/api/v1/Coupons.js +11 -0
  62. package/src/api/v1/Courses.js +2 -0
  63. package/src/api/v1/Hashes.js +3 -2
  64. package/src/api/v1/Telegram.js +6 -0
  65. package/src/api/v1/Users.js +12 -0
  66. package/src/api/v1/index.js +0 -4
  67. package/tests/integration/api/v1/accounts.test.js +5 -11
  68. package/tests/integration/api/v1/auth/login.test.js +18 -34
  69. package/tests/integration/api/v1/conversational-topics.test.js +3 -5
  70. package/tests/integration/api/v1/countries.test.js +1 -1
  71. package/tests/integration/api/v1/coupons.test.js +2 -9
  72. package/tests/integration/api/v1/currencies/index.test.js +1 -1
  73. package/tests/integration/api/v1/files.test.js +18 -15
  74. package/tests/integration/api/v1/hashes.test.js +2 -10
  75. package/tests/integration/api/v1/mail-blacklist.test.js +2 -9
  76. package/tests/integration/api/v1/offers.test.js +2 -9
  77. package/tests/integration/api/v1/organizations.test.js +8 -8
  78. package/tests/integration/api/v1/quizzes/index.test.js +0 -2
  79. package/tests/integration/api/v1/short-links.test.js +2 -9
  80. package/tests/integration/api/v1/taxes.test.js +2 -3
  81. package/tests/integration/api/v1/teacher/index.test.js +1 -1
  82. package/tests/integration/api/v1/telegram.test.js +33 -0
  83. package/tests/integration/api/v1/user-types.test.js +2 -4
  84. package/tests/integration/api/v1/users/index.test.js +10 -13
  85. package/tests/integration/api/v1/users/telegram.test.js +36 -0
  86. package/tests/integration/api/v1/zoom.test.js +2 -9
  87. package/src/api/v1/Categories.js +0 -12
  88. package/src/api/v1/Configs.js +0 -12
  89. package/tests/integration/api/v1/categories.test.js +0 -19
  90. 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-03 10:22:42</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed ">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (3)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed ">3 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/integration/api/v1/accounts.test.js</div><div class="suite-time">2.086s</div></summary><div class="suite-tests"><div class="test-result failed"><div class="test-info"><div class="test-suitename">api/v1/accounts</div><div class="test-title">get | unauthenticated</div><div class="test-status">failed</div><div class="test-duration">0.11s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: HTTP 401
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.&lt;anonymous&gt; (/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.&lt;anonymous&gt; (/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.&lt;anonymous&gt; (/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
- testTimeout: 30000,
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.61",
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": {
@@ -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
  }
@@ -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. */
@@ -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
  }
@@ -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
  }
@@ -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
  }
@@ -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
- logout()
15
- .then(() => api.auth.login({ password })
16
- .then(response => {
17
- // here we have more info of the request because we have the errors
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
- logout()
30
- .then(() => api.auth.login({ email })
31
- .then(response => {
32
- // here we have more info of the request because we have the errors
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
- logout()
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 InsigniaApiV1 from '../../../../src/api/v1/index.js';
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["enabled"]).toBeDefined();
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 InsigniaApiV1 from '../../../../src/api/v1/index.js';
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 login();
5
+ await loginAdmin();
13
6
  await api.coupons.get()
14
7
  .then(response => {
15
8
  response = Object.values(response);
@@ -6,7 +6,7 @@ import {
6
6
 
7
7
  describe('api/v1/currencies', () => {
8
8
  test('get', async () => {
9
- api.currencies.get()
9
+ await api.currencies.get()
10
10
  .then(response => {
11
11
  expect(response["1"]).toBeDefined();
12
12
  expect(response["1"]["id"]).toBeDefined();
@@ -1,21 +1,24 @@
1
1
  import { Blob } from 'buffer';
2
- import InsigniaApiV1 from '../../../../src/api/v1/index.js';
2
+ import { api, loginAdmin } from '../../../helpers.js';
3
3
 
4
- const api = new InsigniaApiV1(process.env.INSIGNIA_EDUCATION_API_BASE_URL);
5
-
6
- const login = () => api.auth.login({
7
- email: process.env.TEST_EMAIL,
8
- password: process.env.TEST_PASSWORD,
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
- test('get | authenticated', async () => {
13
- await login();
14
- await api.files.get()
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
- response = Object.values(response);
17
- expect(Array.isArray(response)).toBe(true);
18
- response.forEach(file => {
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 login();
28
- const blob = new Blob(['test'], { type: 'image/png' });
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 InsigniaApiV1 from '../../../../src/api/v1/index.js';
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 login();
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 InsigniaApiV1 from '../../../../src/api/v1/index.js';
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 login();
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 InsigniaApiV1 from '../../../../src/api/v1/index.js';
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 login();
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
- loginCustomer,
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 loginCustomer();
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 loginCustomer();
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 loginCustomer();
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 loginCustomer();
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 loginCustomer();
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 loginCustomer();
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 loginCustomer();
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 InsigniaApiV1 from '../../../../src/api/v1/index.js';
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 login();
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 InsigniaApiV1 from '../../../../src/api/v1/index.js';
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 login();
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 InsigniaApiV1 from '../../../../src/api/v1/index.js';
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
  });