proxymail-qa 0.1.1 → 0.3.0

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
@@ -86,6 +86,9 @@ cy.task('createAddress').then(({ address }) => {
86
86
  // A persona with a date of birth for an age band, and a country.
87
87
  await qa.createAddress({ persona: true, age: 'under13', country: 'GB' });
88
88
 
89
+ // Find a test account made earlier: an under-18 in GB with a saved password.
90
+ const [teen] = await qa.listAddresses({ tags: ['age-gate'], country: 'GB', maxAge: 17, hasPassword: true });
91
+
89
92
  // The link that opens a mobile app (myapp://…) when the email also has a web one.
90
93
  const { value: appLink } = await qa.waitForLink(address, { app: true });
91
94
 
package/dist/index.d.ts CHANGED
@@ -57,6 +57,16 @@ export interface CreateAddressInput {
57
57
  dateOfBirth?: string;
58
58
  country?: string;
59
59
  }
60
+ export interface ListAddressesOptions {
61
+ limit?: number;
62
+ before?: string;
63
+ tags?: string[];
64
+ country?: string;
65
+ minAge?: number;
66
+ maxAge?: number;
67
+ hasPassword?: boolean;
68
+ search?: string;
69
+ }
60
70
  export interface AddressPatch {
61
71
  label?: string | null;
62
72
  notes?: string | null;
@@ -101,6 +111,32 @@ export interface WaitOptions {
101
111
  since?: Date | string;
102
112
  }
103
113
  export type LinkType = 'verify' | 'magic_signin' | 'invite' | 'reset' | 'any';
114
+ export interface EmailSummary {
115
+ received_at: string;
116
+ service: string | null;
117
+ service_domain: string | null;
118
+ type: 'code' | 'link' | null;
119
+ subtype: string | null;
120
+ email_id: string;
121
+ }
122
+ export interface EmailCount {
123
+ address: string;
124
+ since: string | null;
125
+ count: number;
126
+ emails: EmailSummary[];
127
+ }
128
+ export interface CountOptions {
129
+ since?: Date | string;
130
+ service?: string;
131
+ type?: 'code' | 'link';
132
+ subtype?: CountSubtype | CountSubtype[];
133
+ }
134
+ export type CountSubtype = 'otp' | 'coupon' | 'verify' | 'magic_signin' | 'invite' | 'reset' | 'other' | 'any';
135
+ export interface NoEmail {
136
+ address: string;
137
+ since: string;
138
+ until: string;
139
+ }
104
140
  export declare class ProxyMailQAError extends Error {
105
141
  readonly status: number;
106
142
  readonly code: string;
@@ -120,10 +156,7 @@ export declare class ProxyMailQA {
120
156
  password: true;
121
157
  }): Promise<CreatedAddressWithPassword>;
122
158
  createAddress(input?: CreateAddressInput): Promise<CreatedAddress>;
123
- listAddresses(options?: {
124
- limit?: number;
125
- before?: string;
126
- }): Promise<Address[]>;
159
+ listAddresses(options?: ListAddressesOptions): Promise<Address[]>;
127
160
  getAddress(address: string): Promise<Address>;
128
161
  updateAddress(address: string, patch: AddressPatch): Promise<Address>;
129
162
  deleteAddress(address: string): Promise<void>;
@@ -137,6 +170,10 @@ export declare class ProxyMailQA {
137
170
  includeHtml?: boolean;
138
171
  limit?: number;
139
172
  }): Promise<Record<string, unknown>[]>;
173
+ countEmails(address: string, options?: CountOptions): Promise<EmailCount>;
174
+ expectNoEmail(address: string, options?: CountOptions & {
175
+ timeout?: number;
176
+ }): Promise<NoEmail>;
140
177
  checkDelivery(address: string): Promise<DeliveryStatus>;
141
178
  variants(address: string): Promise<Variants>;
142
179
  setPassword(address: string, options: {
package/dist/index.js CHANGED
@@ -22,6 +22,19 @@ export class ProxyMailQAError extends Error {
22
22
  }
23
23
  }
24
24
  const TIMED_OUT = /^No (code|link) arrived/;
25
+ function countQuery(options, extra = {}) {
26
+ const query = new URLSearchParams(extra);
27
+ if (options.since !== undefined)
28
+ query.set('since', new Date(options.since).toISOString());
29
+ if (options.service)
30
+ query.set('service', options.service);
31
+ if (options.type)
32
+ query.set('type', options.type);
33
+ const subtype = [options.subtype ?? []].flat().join(',');
34
+ if (subtype)
35
+ query.set('subtype', subtype);
36
+ return query.size ? `?${query}` : '';
37
+ }
25
38
  const MAX_WAIT_SECONDS = 30;
26
39
  export class ProxyMailQA {
27
40
  apiKey;
@@ -66,12 +79,26 @@ export class ProxyMailQA {
66
79
  country: input.country,
67
80
  });
68
81
  }
82
+ // Newest first. With filters, finds test accounts made earlier, such as
83
+ // an under-18 in GB with a saved password: every filter given must match.
69
84
  listAddresses(options = {}) {
70
85
  const query = new URLSearchParams();
71
86
  if (options.limit)
72
87
  query.set('limit', String(options.limit));
73
88
  if (options.before)
74
89
  query.set('before', options.before);
90
+ for (const tag of options.tags ?? [])
91
+ query.append('tag', tag);
92
+ if (options.country)
93
+ query.set('country', options.country);
94
+ if (options.minAge !== undefined)
95
+ query.set('min_age', String(options.minAge));
96
+ if (options.maxAge !== undefined)
97
+ query.set('max_age', String(options.maxAge));
98
+ if (options.hasPassword !== undefined)
99
+ query.set('has_password', String(options.hasPassword));
100
+ if (options.search)
101
+ query.set('search', options.search);
75
102
  return this.request('GET', `/addresses${query.size ? `?${query}` : ''}`);
76
103
  }
77
104
  getAddress(address) {
@@ -107,6 +134,32 @@ export class ProxyMailQA {
107
134
  query.set('limit', String(options.limit));
108
135
  return this.request('GET', `/addresses/${encodeURIComponent(address)}/messages${query.size ? `?${query}` : ''}`);
109
136
  }
137
+ // How many emails reached the address (since a time, from a service, with a
138
+ // code or link), each summarised without its content. Doesn't wait: count
139
+ // after the wait for the first email returns, so a duplicate has had time
140
+ // to arrive. Without since, every email the address has received.
141
+ countEmails(address, options = {}) {
142
+ return this.request('GET', `/addresses/${encodeURIComponent(address)}/count${countQuery(options)}`);
143
+ }
144
+ // Resolves when no matching email arrives within timeout seconds (default
145
+ // 30; longer repeats the request), for checks such as "an unsubscribed user
146
+ // gets nothing". Throws ProxyMailQAError (status 409, code email_arrived)
147
+ // as soon as one does. Pass since as the time of the action, so an email
148
+ // that came before the call still counts; without it, from now.
149
+ async expectNoEmail(address, options = {}) {
150
+ const deadline = Date.now() + (options.timeout ?? MAX_WAIT_SECONDS) * 1000;
151
+ let since = options.since;
152
+ for (;;) {
153
+ const left = Math.max(0, Math.min(MAX_WAIT_SECONDS, Math.ceil((deadline - Date.now()) / 1000)));
154
+ const query = countQuery({ ...options, since }, { timeout: String(left) });
155
+ const result = await this.request('GET', `/addresses/${encodeURIComponent(address)}/none${query}`);
156
+ // The server's since, so later requests watch from the same moment
157
+ // whatever this machine's clock says.
158
+ since = result.since;
159
+ if (Date.now() >= deadline - 500)
160
+ return { address: result.address, since: result.since, until: result.until };
161
+ }
162
+ }
110
163
  // What reached the address and what was refused, without waiting: for when
111
164
  // a wait comes back empty.
112
165
  checkDelivery(address) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proxymail-qa",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Disposable test inboxes for Playwright, Cypress and scripts: create an address, then wait for its verification code or link.",
5
5
  "license": "MIT",
6
6
  "author": "Proxy Mail QA <hello@proxymail.qa> (https://proxymail.qa)",