cypress-mailisk 2.0.1 → 2.2.1

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/README.md CHANGED
@@ -1,17 +1,32 @@
1
1
  # cypress-mailisk
2
2
 
3
- ## Install with npm
3
+ Mailisk is an end-to-end email and SMS testing platform. It allows you to receive emails and SMS messages with code to automate tests.
4
+
5
+ - Get a unique subdomain and unlimited email addresses for free.
6
+ - Easily automate E2E password reset and account verification by catching emails.
7
+ - Receive SMS messages and automate SMS tests.
8
+ - Virtual SMTP and SMS support to test without 3rd party clients.
9
+
10
+ ## Get started
11
+
12
+ For a more step-by-step walkthrough see the [Cypress Guide](https://docs.mailisk.com/guides/cypress.html).
13
+
14
+ ### Installation
15
+
16
+ #### Install with npm
4
17
 
5
18
  ```shell
6
19
  npm install --save-dev cypress-mailisk
7
20
  ```
8
21
 
9
- ## Install with Yarn
22
+ #### Install with Yarn
10
23
 
11
24
  ```shell
12
25
  yarn add cypress-mailisk --dev
13
26
  ```
14
27
 
28
+ #### Add to Cypress
29
+
15
30
  After installing the package add the following in your project's `cypress/support/e2e.js`:
16
31
 
17
32
  ```js
@@ -36,78 +51,205 @@ The cypress-mailisk plugin provides additional commands which can be accessed on
36
51
 
37
52
  ### cy.mailiskSearchInbox
38
53
 
39
- This is the main command to interact with Mailisk, it wraps the [Search Inbox](/api-reference/search-inbox) endpoint.
54
+ This is the main command to interact with Mailisk, it wraps the [Search Inbox](/api-reference/search-inbox) endpoint. See the reference documentation for the full list of filters and their description.
40
55
 
41
56
  ```js
42
- cy.mailiskSearchInbox('yournamespace', { to_addr_prefix: 'test.user@' }).then((response) => {
43
- const emails = response.data;
44
- // ...
45
- });
57
+ cy.mailiskSearchInbox('yournamespace', { to_addr_prefix: 'test.user@', subject_includes: 'register' }).then(
58
+ (response) => {
59
+ const emails = response.data;
60
+ expect(emails).to.not.be.empty;
61
+ },
62
+ );
46
63
  ```
47
64
 
48
- This Cypress command does a few extra things out of the box compared to calling the raw API directly:
65
+ This command does a few extra things out of the box:
49
66
 
50
- - By default it uses the `wait` flag. This means the call won't return until at least one email is received. Disabling this flag via `wait: false` can cause it to return an empty response immediately.
51
- - The request timeout is adjustable by passing `timeout` in the request options. By default it uses a timeout of 5 minutes.
52
- - By default it returns emails in the last 15 minutes. This ensures that only new emails are returned. This can be overriden by passing the `from_timestamp` parameter (`from_timestamp: 0` will disable filtering by email age).
67
+ - Waits until at least one new email arrives (override with `wait: false`).
68
+ - Times out after 5 minutes if nothing shows up (adjust via `requestOptions.timeout`).
69
+ - Ignores messages older than 15 minutes to avoid picking up leftovers from previous tests (change via `from_timestamp`).
70
+
71
+ #### Quick examples
53
72
 
54
73
  ```js
55
- // timeout of 5 minute
74
+ // wait up to the default 5 min for *any* new mail
56
75
  cy.mailiskSearchInbox(namespace);
57
- // timeout of 1 minute
76
+ // custom 60-second timeout
58
77
  cy.mailiskSearchInbox(namespace, {}, { timeout: 1000 * 60 });
59
- // returns immediately, even if the result would be empty
78
+ // polling pattern — return immediately, even if inbox is empty
60
79
  cy.mailiskSearchInbox(namespace, { wait: false });
80
+ // returns the last 20 emails in the namespace immediately
81
+ cy.mailiskSearchInbox(namespace, { wait: false, from_timestamp: 0, limit: 20 });
61
82
  ```
62
83
 
63
- For the full list of filters and their description see the [Search Inbox](/api-reference/search-inbox#request-1) endpoint reference.
64
-
65
- ### Filter by TO address
66
-
67
- The `to_addr_prefix` option allows filtering by the email's TO address. Specifically the TO address has to start with this.
84
+ #### Filter by destination address
68
85
 
69
- For example, if someone sends an email to `my-user-1@yournamespace.mailisk.net`, you can filter it by using `my-user-1@`:
86
+ A common pattern is to wait for the email your UI just triggered (e.g. password-reset).
87
+ Pass `to_addr_prefix` so you don’t pick up stale messages:
70
88
 
71
89
  ```js
72
90
  cy.mailiskSearchInbox(namespace, {
73
- to_addr_prefix: 'my-user-1@',
91
+ to_addr_prefix: `test.user@${namespace}.mailisk.net`,
92
+ }).then((response) => {
93
+ const emails = response.data;
94
+ expect(emails).to.not.be.empty;
74
95
  });
75
96
  ```
76
97
 
77
- ### Filter by FROM address
78
-
79
- The `from_addr_includes` option allows filtering by the email's FROM address. Specifically the TO address has to include this. Note that this is different from the to address as it is _includes_ not _prefix_.
98
+ #### Filter by sender address
80
99
 
81
- For example, if someone sends an email from the `example.com` domain we could filter like so:
100
+ Use `from_addr_includes` to narrow results to a sender or domain. This is useful when multiple systems write to the same namespace but only one sender matters for the test.
82
101
 
83
102
  ```js
84
103
  cy.mailiskSearchInbox(namespace, {
85
104
  from_addr_includes: '@example.com',
105
+ }).then((response) => {
106
+ const emails = response.data;
107
+ expect(emails).to.not.be.empty;
86
108
  });
87
109
  ```
88
110
 
89
- If we know a specific email address we want to listen to we can do this:
111
+ #### Filter by subject contents
112
+
113
+ The `subject_includes` filter helps when the UI sends different notification types from the same sender. Matching is case-insensitive and only requires the provided string to be present.
90
114
 
91
115
  ```js
92
116
  cy.mailiskSearchInbox(namespace, {
93
- from_addr_includes: 'no-reply@example.com',
117
+ to_addr_prefix: `test.user@${namespace}.mailisk.net`,
118
+ subject_includes: 'password reset',
119
+ }).then((response) => {
120
+ const emails = response.data;
121
+ expect(emails).to.not.be.empty;
94
122
  });
95
123
  ```
96
124
 
97
- ### Filter by Subject
125
+ ### cy.mailiskGetAttachment
98
126
 
99
- The `subject_includes` option allows filtering by the email's Subject. Specifically the Subject has to include this (case-insensitive).
127
+ This command retrieves attachment metadata including download URL, filename, content type, and size.
100
128
 
101
- If we're testing password reset that sends an email with the subject `Password reset request`. We could filter by something like this:
129
+ ```js
130
+ cy.mailiskGetAttachment('attachment-id').then((attachment) => {
131
+ console.log(attachment.data.filename);
132
+ console.log(attachment.data.content_type);
133
+ console.log(attachment.data.size);
134
+ });
135
+ ```
136
+
137
+ ### cy.mailiskDownloadAttachment
138
+
139
+ This command downloads the actual attachment content as a Buffer. It first retrieves the attachment metadata, then downloads the file from the provided download URL.
102
140
 
103
141
  ```js
104
- cy.mailiskSearchInbox(namespace, {
105
- subject_includes: 'password reset request',
142
+ cy.mailiskDownloadAttachment('attachment-id').then((buffer) => {
143
+ cy.writeFile('downloads/attachment.pdf', buffer);
144
+ });
145
+ ```
146
+
147
+ ### cy.mailiskSearchSms
148
+
149
+ This is the main command to interact with Mailisk SMS, it wraps the [Search SMS](/api-reference/search-sms) endpoint. Use a phone number that is registered to your account.
150
+
151
+ ```js
152
+ cy.mailiskSearchSms('phoneNumber', { from_date: new Date('2023-01-01T00:00:00Z'), limit: 5 }).then((response) => {
153
+ const smsMessages = response.data;
154
+ });
155
+ ```
156
+
157
+ This Cypress command does a few extra things out of the box compared to calling the raw API directly:
158
+
159
+ - Waits until at least one SMS matches the filters (override with `wait: false`).
160
+ - Times out after 5 minutes if nothing shows up (adjust via `requestOptions.timeout`).
161
+ - Ignores SMS older than 15 minutes by default (override with `from_date`).
162
+
163
+ #### Quick examples
164
+
165
+ ```js
166
+ // wait up to the default 5 min for *any* new SMS sent to the phone number
167
+ cy.mailiskSearchSms('phoneNumber');
168
+ // custom 60-second timeout
169
+ cy.mailiskSearchSms('phoneNumber', {}, { timeout: 1000 * 60 });
170
+ // polling pattern — return immediately, even if inbox is empty
171
+ cy.mailiskSearchSms('phoneNumber', { wait: false });
172
+ // returns the last 20 SMS messages for the phone number immediately
173
+ cy.mailiskSearchSms('phoneNumber', { wait: false, from_date: new Date(0), limit: 20 });
174
+ ```
175
+
176
+ #### Filter by sender phone number
177
+
178
+ Use `from_number` to narrow results to a specific phone number.
179
+
180
+ ```js
181
+ cy.mailiskSearchSms('phoneNumber', {
182
+ from_number: '1234567890',
183
+ }).then((response) => {
184
+ const smsMessages = response.data;
185
+ });
186
+ ```
187
+
188
+ #### Filter by body contents
189
+
190
+ Use `body` to narrow results to a specific message body.
191
+
192
+ ```js
193
+ cy.mailiskSearchSms('phoneNumber', {
194
+ body: 'Here is your code:',
195
+ }).then((response) => {
196
+ const smsMessages = response.data;
197
+ });
198
+ ```
199
+
200
+ ### cy.mailiskListSmsNumbers
201
+
202
+ This command lists the SMS numbers available to the current API key.
203
+
204
+ ```js
205
+ cy.mailiskListSmsNumbers().then((response) => {
206
+ const smsNumbers = response.data;
207
+ expect(smsNumbers).to.not.be.empty;
106
208
  });
107
209
  ```
108
210
 
109
211
  ## Common test cases
110
212
 
213
+ ### Working with email attachments
214
+
215
+ This example demonstrates how to search for emails with attachments and download them:
216
+
217
+ ```js
218
+ describe('Test email attachments', () => {
219
+ const namespace = 'yournamespace';
220
+ const testEmailAddr = `test.user@${namespace}.mailisk.net`;
221
+
222
+ it('Finds email with attachment and downloads it', () => {
223
+ cy.mailiskSearchInbox(namespace, {
224
+ to_addr_prefix: testEmailAddr,
225
+ subject_includes: 'invoice',
226
+ }).then((response) => {
227
+ expect(response.data).to.not.be.empty;
228
+ const email = response.data[0];
229
+
230
+ // Check if email has attachments
231
+ expect(email.attachments).to.not.be.empty;
232
+ const attachment = email.attachments[0];
233
+
234
+ // Get attachment metadata
235
+ cy.mailiskGetAttachment(attachment.id).then((attachmentData) => {
236
+ expect(attachmentData.data.filename).to.contain('.pdf');
237
+ expect(attachmentData.data.content_type).to.equal('application/pdf');
238
+
239
+ // Download the attachment
240
+ cy.mailiskDownloadAttachment(attachment.id).then((buffer) => {
241
+ // Save to downloads folder
242
+ cy.writeFile(`downloads/${attachmentData.data.filename}`, buffer);
243
+
244
+ // Verify file was downloaded
245
+ cy.readFile(`downloads/${attachmentData.data.filename}`).should('exist');
246
+ });
247
+ });
248
+ });
249
+ });
250
+ });
251
+ ```
252
+
111
253
  ### Password reset page
112
254
 
113
255
  This example demonstrates going to a password reset page, requesting a new password, receiving reset code link via email and finally setting the new password.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cypress-mailisk",
3
- "version": "2.0.1",
3
+ "version": "2.2.1",
4
4
  "description": "Mailisk library for Cypress",
5
5
  "keywords": [
6
6
  "mailisk",
@@ -13,7 +13,10 @@
13
13
  "main": "index.js",
14
14
  "types": "src/mailiskCommands.d.ts",
15
15
  "scripts": {
16
- "tsc": "node_modules/.bin/tsc src/mailiskCommands.d.ts --types node"
16
+ "tsc": "node_modules/.bin/tsc src/mailiskCommands.d.ts --types node",
17
+ "test": "jest",
18
+ "test:watch": "jest --watch",
19
+ "test:coverage": "jest --coverage"
17
20
  },
18
21
  "peerDependencies": {
19
22
  "cypress": ">= 2.1.0"
@@ -32,6 +35,8 @@
32
35
  },
33
36
  "homepage": "https://github.com/mailisk-app/cypress-mailisk#readme",
34
37
  "devDependencies": {
35
- "typescript": "^4.7.4"
38
+ "typescript": "^4.7.4",
39
+ "jest": "^29.0.0",
40
+ "@jest/globals": "^29.0.0"
36
41
  }
37
42
  }
@@ -7,6 +7,17 @@ export interface EmailAddress {
7
7
  name?: string;
8
8
  }
9
9
 
10
+ export interface EmailAttachment {
11
+ /** Unique identifier for the attachment */
12
+ id: string;
13
+ /** Filename of the attachment */
14
+ filename: string;
15
+ /** Content type of the attachment */
16
+ content_type: string;
17
+ /** Size in bytes of the attachment */
18
+ size: number;
19
+ }
20
+
10
21
  export interface Email {
11
22
  /** Namespace scoped ID */
12
23
  id: string;
@@ -32,6 +43,46 @@ export interface Email {
32
43
  expires_timestamp: number;
33
44
  /** The spam score as reported by SpamAssassin */
34
45
  spam_score?: number;
46
+ /** The headers of the email */
47
+ headers?: Record<string, string>;
48
+ /** The attachments of the email */
49
+ attachments?: EmailAttachment[];
50
+ }
51
+
52
+ export interface SmsMessage {
53
+ /** Unique identifier for the message */
54
+ id: string;
55
+ /** Unique identifier for the SMS phone number */
56
+ sms_phone_number_id: string;
57
+ /** Body of the message */
58
+ body: string;
59
+ /** From number of the message */
60
+ from_number: string;
61
+ /** To number of the message */
62
+ to_number: string;
63
+ /** Provider message ID */
64
+ provider_message_id?: string;
65
+ /** Date and time the message was created */
66
+ created_at: string;
67
+ /** Direction of the message */
68
+ direction: 'inbound' | 'outbound';
69
+ }
70
+
71
+ export interface SmsNumber {
72
+ /** Unique identifier for the SMS number */
73
+ id: string;
74
+ /** Unique identifier for the organisation */
75
+ organisation_id: string;
76
+ /** Status of the SMS number */
77
+ status: 'requested' | 'active' | 'disabled';
78
+ /** Country of the SMS number */
79
+ country: string;
80
+ /** SMS Phone number */
81
+ phone_number?: string;
82
+ /** Date and time the SMS number was created */
83
+ created_at: string;
84
+ /** Date and time the SMS number was updated */
85
+ updated_at: string;
35
86
  }
36
87
 
37
88
  export interface SearchInboxParams {
@@ -92,9 +143,87 @@ export interface SearchInboxResponse {
92
143
  data: Email[];
93
144
  }
94
145
 
146
+ export interface SmtpSettings {
147
+ data: {
148
+ host: string;
149
+ port: number;
150
+ username: string;
151
+ password: string;
152
+ };
153
+ }
154
+
155
+ export interface GetAttachmentResponse {
156
+ data: {
157
+ id: string;
158
+ filename: string;
159
+ content_type: string;
160
+ size: number;
161
+ expires_at: string | null;
162
+ download_url: string;
163
+ };
164
+ }
165
+
166
+ export interface ListNamespacesResponse {
167
+ total_count: number;
168
+ data: { id: string; namespace: string }[];
169
+ }
170
+
171
+ export interface SearchSmsMessagesParams {
172
+ /**
173
+ * The maximum number of SMS messages returned (1-100), used alongside `offset` for pagination.
174
+ */
175
+ limit?: number;
176
+ /**
177
+ * The number of SMS messages to skip/ignore, used alongside `limit` for pagination.
178
+ */
179
+ offset?: number;
180
+ /**
181
+ * Filter messages by body contents (case insensitive).
182
+ */
183
+ body?: string;
184
+ /**
185
+ * Filter messages by sender phone number prefix.
186
+ */
187
+ from_number?: string;
188
+ /**
189
+ * Filter messages created on or after this date (Date object or ISO 8601 string).
190
+ */
191
+ from_date?: Date | string;
192
+ /**
193
+ * Filter messages created on or before this date (Date object or ISO 8601 string).
194
+ */
195
+ to_date?: Date | string;
196
+ /**
197
+ * When true, keep the request open until at least one SMS is returned.
198
+ */
199
+ wait?: boolean;
200
+ }
201
+
202
+ export interface SearchSmsMessagesResponse {
203
+ total_count: number;
204
+ options: SearchSmsMessagesParams;
205
+ data: SmsMessage[];
206
+ }
207
+
208
+ export interface ListSmsNumbersResponse {
209
+ total_count: number;
210
+ data: SmsNumber[];
211
+ }
212
+
213
+ export interface SendVirtualSmsParams {
214
+ /** The phone number to send the SMS from */
215
+ from_number: string;
216
+ /** The phone number to send the SMS to */
217
+ to_number: string;
218
+ /** The body of the SMS message */
219
+ body: string;
220
+ }
221
+
95
222
  declare global {
96
223
  namespace Cypress {
97
224
  interface Chainable {
225
+ mailiskListNamespaces(): Cypress.Chainable<ListNamespacesResponse>;
226
+
98
227
  mailiskSearchInbox(
99
228
  /**
100
229
  * The unique namespace to search.
@@ -111,6 +240,58 @@ declare global {
111
240
  */
112
241
  options?: Partial<Cypress.RequestOptions>,
113
242
  ): Cypress.Chainable<SearchInboxResponse>;
243
+
244
+ mailiskGetAttachment(
245
+ /**
246
+ * The attachment ID to retrieve.
247
+ */
248
+ attachmentId: string,
249
+ /**
250
+ * Request options.
251
+ *
252
+ * See https://docs.cypress.io/api/commands/request#Arguments
253
+ */
254
+ options?: Partial<Cypress.RequestOptions>,
255
+ ): Cypress.Chainable<GetAttachmentResponse>;
256
+
257
+ mailiskDownloadAttachment(
258
+ /**
259
+ * The attachment ID to download.
260
+ */
261
+ attachmentId: string,
262
+ /**
263
+ * Request options.
264
+ *
265
+ * See https://docs.cypress.io/api/commands/request#Arguments
266
+ */
267
+ options?: Partial<Cypress.RequestOptions>,
268
+ ): Cypress.Chainable<Buffer>;
269
+
270
+ mailiskSearchSms(
271
+ /**
272
+ * The phone number to search.
273
+ */
274
+ phoneNumber: string,
275
+ /**
276
+ * Search parameters.
277
+ */
278
+ params?: SearchSmsMessagesParams,
279
+ /**
280
+ * Request options.
281
+ *
282
+ * See https://docs.cypress.io/api/commands/request#Arguments
283
+ */
284
+ options?: Partial<Cypress.RequestOptions>,
285
+ ): Cypress.Chainable<SearchSmsMessagesResponse>;
286
+
287
+ mailiskListSmsNumbers(
288
+ /**
289
+ * Request options.
290
+ *
291
+ * See https://docs.cypress.io/api/commands/request#Arguments
292
+ */
293
+ options?: Partial<Cypress.RequestOptions>,
294
+ ): Cypress.Chainable<ListSmsNumbersResponse>;
114
295
  }
115
296
  }
116
297
  }
@@ -2,23 +2,31 @@ const Request = require('./request');
2
2
 
3
3
  class MailiskCommands {
4
4
  static get cypressCommands() {
5
- return ['mailiskSetApiKey', 'mailiskListNamespaces', 'mailiskSearchInbox'];
5
+ return [
6
+ 'mailiskSetApiKey',
7
+ 'mailiskListNamespaces',
8
+ 'mailiskSearchInbox',
9
+ 'mailiskGetAttachment',
10
+ 'mailiskDownloadAttachment',
11
+ 'mailiskSearchSms',
12
+ 'mailiskListSmsNumbers',
13
+ ];
6
14
  }
7
15
 
8
16
  constructor() {
9
- const defaultApiKey = Cypress.env('MAILISK_API_KEY');
10
- this.mailiskSetApiKey(defaultApiKey);
17
+ const apiKey = Cypress.env('MAILISK_API_KEY');
18
+ this.mailiskSetApiKey(apiKey);
11
19
  }
12
20
 
13
21
  mailiskSetApiKey(apiKey) {
14
- this.request = new Request({ apiKey, baseUrl: Cypress.env('MAILISK_API_URL') });
22
+ this.request = new Request({ apiKey, apiUrl: Cypress.env('MAILISK_API_URL') });
15
23
  }
16
24
 
17
25
  mailiskListNamespaces() {
18
26
  return this.request.get('api/namespaces');
19
27
  }
20
28
 
21
- _mailiskSearchAction(namespace, _options, urlParams, startTime, nextTimeout) {
29
+ _mailiskSearchInboxAction(namespace, _options, urlParams, startTime, nextTimeout) {
22
30
  return this.request
23
31
  .get(`api/emails/${namespace}/inbox?${urlParams.toString()}`, { ..._options, timeout: nextTimeout })
24
32
  .then((response) => {
@@ -27,27 +35,27 @@ class MailiskCommands {
27
35
  }
28
36
  const timeout = Math.max(_options.timeout - (Date.now() - startTime), 1);
29
37
  cy.wait(Math.min(timeout, 9000), { log: false });
30
- return this._mailiskSearchAction(namespace, _options, urlParams, startTime, timeout);
38
+ return this._mailiskSearchInboxAction(namespace, _options, urlParams, startTime, timeout);
31
39
  });
32
40
  }
33
41
 
34
- mailiskSearchInbox(namespace, params, options = {}) {
42
+ mailiskSearchInbox(namespace, params = {}, options = {}) {
35
43
  let _params = { ...params };
36
44
 
37
45
  // default from_timestamp, 15 minutes before starting this request
38
- if (params.from_timestamp == null) {
39
- _params.from_timestamp = Math.floor(new Date().getTime() / 1000) - 15 * 60;
46
+ if (_params.from_timestamp == null) {
47
+ _params.from_timestamp = Math.floor(Date.now() / 1000) - 15 * 60;
40
48
  }
41
49
 
42
50
  // by default wait for email
43
- if (params.wait !== false) {
51
+ if (_params.wait !== false) {
44
52
  _params.wait = true;
45
53
  }
46
54
 
47
55
  const urlParams = new URLSearchParams();
48
56
  for (const key in _params) {
49
57
  const value = _params[key];
50
- if (value) urlParams.set(key, value.toString());
58
+ if (value !== undefined && value !== null) urlParams.set(key, value.toString());
51
59
  }
52
60
 
53
61
  let _options = { ...options };
@@ -61,11 +69,81 @@ class MailiskCommands {
61
69
  if (_params.wait) {
62
70
  urlParams.delete('wait');
63
71
  const startTime = Date.now();
64
- return this._mailiskSearchAction(namespace, _options, urlParams, startTime, _options.timeout);
72
+ return this._mailiskSearchInboxAction(namespace, _options, urlParams, startTime, _options.timeout);
65
73
  } else {
66
74
  return this.request.get(`api/emails/${namespace}/inbox?${urlParams.toString()}`, _options);
67
75
  }
68
76
  }
77
+
78
+ mailiskGetAttachment(attachmentId, options = {}) {
79
+ return this.request.get(`api/attachments/${attachmentId}`, options);
80
+ }
81
+
82
+ mailiskDownloadAttachment(attachmentId, options = {}) {
83
+ return this.mailiskGetAttachment(attachmentId, options).then((attachment) => {
84
+ return this.request.getBinary(attachment.data.download_url, options);
85
+ });
86
+ }
87
+
88
+ _mailiskSearchSmsAction(phoneNumber, _options, urlParams, startTime, nextTimeout) {
89
+ return this.request
90
+ .get(`api/sms/${phoneNumber}/messages?${urlParams.toString()}`, { ..._options, timeout: nextTimeout })
91
+ .then((response) => {
92
+ if (response.total_count !== 0) {
93
+ return response;
94
+ }
95
+ const timeout = Math.max(_options.timeout - (Date.now() - startTime), 1);
96
+ cy.wait(Math.min(timeout, 9000), { log: false });
97
+ return this._mailiskSearchSmsAction(phoneNumber, _options, urlParams, startTime, timeout);
98
+ });
99
+ }
100
+
101
+ mailiskSearchSms(phoneNumber, params = {}, options = {}) {
102
+ let _params = { ...params };
103
+
104
+ // default from_date, 15 minutes before starting this request
105
+ if (_params.from_date == null) {
106
+ _params.from_date = new Date(Date.now() - 15 * 60 * 1000);
107
+ }
108
+
109
+ // by default wait for email
110
+ if (params.wait !== false) {
111
+ _params.wait = true;
112
+ }
113
+
114
+ if (_params.from_date instanceof Date) {
115
+ _params.from_date = _params.from_date.toISOString();
116
+ }
117
+ if (_params.to_date instanceof Date) {
118
+ _params.to_date = _params.to_date.toISOString();
119
+ }
120
+
121
+ const urlParams = new URLSearchParams();
122
+ for (const key in _params) {
123
+ const value = _params[key];
124
+ if (value !== undefined && value !== null) urlParams.set(key, value.toString());
125
+ }
126
+
127
+ let _options = { ...options };
128
+
129
+ // by default wait 5 minutes for emails
130
+ if (_params.wait && !options.timeout) {
131
+ _options.timeout = 1000 * 60 * 5;
132
+ }
133
+
134
+ // temporary workaround due cypress not supporting overriding maxRedirects
135
+ if (_params.wait) {
136
+ urlParams.delete('wait');
137
+ const startTime = Date.now();
138
+ return this._mailiskSearchSmsAction(phoneNumber, _options, urlParams, startTime, _options.timeout);
139
+ } else {
140
+ return this.request.get(`api/sms/${phoneNumber}/messages?${urlParams.toString()}`, _options);
141
+ }
142
+ }
143
+
144
+ mailiskListSmsNumbers(options = {}) {
145
+ return this.request.get(`api/sms/numbers`, options);
146
+ }
69
147
  }
70
148
 
71
149
  module.exports = MailiskCommands;
package/src/request.js CHANGED
@@ -81,6 +81,24 @@ class Request {
81
81
  del(path, opts) {
82
82
  return this.request('DELETE', path, undefined, opts);
83
83
  }
84
+
85
+ getBinary(url, opts) {
86
+ // For binary downloads, we need to use the full URL directly
87
+ const options = {
88
+ method: 'GET',
89
+ url: url,
90
+ encoding: null, // This tells Cypress to return binary data
91
+ ...opts,
92
+ };
93
+ options.failOnStatusCode = false;
94
+ return cy.request(options).then((response) => {
95
+ if (response.isOkStatusCode) {
96
+ return Buffer.from(response.body);
97
+ }
98
+ // Use the same error handling as regular requests
99
+ return this.getResponseHandler()(response);
100
+ });
101
+ }
84
102
  }
85
103
 
86
104
  module.exports = Request;