• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / blurts-server / 00f7475d-b1a7-44af-928a-2bcf33438993

pending completion
00f7475d-b1a7-44af-928a-2bcf33438993

Pull #2989

circleci

Vincent
fixup! fixup! fixup! fixup! Store exposure scan data at OneRep
Pull Request #2989: Add API to store data to scan for exposures

282 of 1740 branches covered (16.21%)

Branch coverage included in aggregate %.

117 of 117 new or added lines in 9 files covered. (100.0%)

959 of 4624 relevant lines covered (20.74%)

3.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/external/onerep.js
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4

5
import AppConstants from '../app-constants.js'
6
import mozlog from '../utils/log.js'
7
import { parseE164PhoneNumber, parseIso8601Datetime } from '../utils/parse.js'
8
import { usStates } from '../utils/states.js'
9
const log = mozlog('external.onerep')
×
10

11
/**
12
 * @param {string} path
13
 * @param {Parameters<typeof fetch>[1]} [options]
14
 */
15
async function onerepFetch (path, options = {}) {
×
16
  const url = 'https://api.onerep.com' + path
×
17
  const headers = new Headers(options.headers)
×
18
  headers.set('Authorization', `Basic ${Buffer.from(`${AppConstants.ONEREP_API_KEY}:`).toString('base64')}`)
×
19
  headers.set('Accept', 'application/json')
×
20
  headers.set('Content-Type', 'application/json')
×
21
  return fetch(url, { ...options, headers })
×
22
}
23

24
/**
25
 * @typedef {object} ProfileData
26
 * @property {string} first_name
27
 * @property {string} last_name
28
 * @property {string} city
29
 * @property {import('../utils/states').StateAbbr} state
30
 * @property {import('../utils/parse.js').ISO8601DateString} [birth_date]
31
 * @property {import('../utils/parse.js').E164PhoneNumberString} [phone_number]
32
 */
33

34
/**
35
 * @param {ProfileData} profileData
36
 * @returns {Promise<number>} Profile ID
37
 */
38
export async function createProfile (profileData) {
39
  /**
40
   * See https://docs.onerep.com/#operation/createProfile
41
   *
42
   * @type {any}
43
   */
44
  const requestBody = {
×
45
    first_name: profileData.first_name,
46
    last_name: profileData.last_name,
47
    addresses: [
48
      {
49
        state: profileData.state,
50
        city: profileData.city
51
      }
52
    ]
53
  }
54
  if (profileData.birth_date) {
×
55
    requestBody.birth_date = profileData.birth_date
×
56
  }
57
  if (profileData.phone_number) {
×
58
    requestBody.phone_numbers = [
×
59
      { number: profileData.phone_number }
60
    ]
61
  }
62
  const response = await onerepFetch('/profiles', {
×
63
    method: 'POST',
64
    body: JSON.stringify(requestBody)
65
  })
66
  if (!response.ok) {
×
67
    log.info(`Failed to create OneRep profile: [${response.status}] [${response.statusText}]`)
×
68
    throw new Error(`Failed to create OneRep profile: [${response.status}] [${response.statusText}]`)
×
69
  }
70
  /**
71
   * See https://docs.onerep.com/#operation/createProfile
72
   *
73
   * @type {{
74
   *   id: number,
75
   *   status: 'active' | 'inactive',
76
   *   created_at: import('../utils/parse.js').ISO8601DateString,
77
   *   updated_at: import('../utils/parse.js').ISO8601DateString,
78
   *   url: string,
79
   * }}
80
   */
81
  const savedProfile = await response.json()
×
82
  return savedProfile.id
×
83
}
84

85
/**
86
 * @param {any} body
87
 * @returns {ProfileData | null}
88
 */
89
export function parseExposureScanData (body) {
90
  const state = usStates.find(state => typeof body === 'object' && state === body.state)
×
91
  if (
×
92
    typeof body !== 'object' ||
×
93
    typeof body.first_name !== 'string' ||
94
    body.first_name.length === 0 ||
95
    typeof body.last_name !== 'string' ||
96
    body.last_name.length === 0 ||
97
    typeof body.city !== 'string' ||
98
    body.city.length === 0 ||
99
    typeof body.state !== 'string' ||
100
    typeof state !== 'string' ||
101
    (typeof body.birth_date !== 'string' && typeof body.birth_date !== 'undefined') ||
102
    (typeof body.phone_number !== 'string' && typeof body.phone_number !== 'undefined')
103
  ) {
104
    return null
×
105
  }
106

107
  return {
×
108
    first_name: body.first_name,
109
    last_name: body.last_name,
110
    city: body.city,
111
    state,
112
    birth_date: parseIso8601Datetime(body.birth_date)?.toISOString(),
113
    phone_number: parseE164PhoneNumber(body.phone_number) ?? undefined
×
114
  }
115
}
116

117
/**
118
 * @param {number} profileId
119
 * @returns {Promise<void>}
120
 */
121
export async function activateProfile (profileId) {
122
  /**
123
   * See https://docs.onerep.com/#operation/activateProfile
124
   *
125
   * @type {any}
126
   */
127
  const response = await onerepFetch(`/profiles/${profileId}/activate`, {
×
128
    method: 'PUT'
129
  })
130
  if (!response.ok) {
×
131
    log.info(`Failed to activate OneRep profile: [${response.status}] [${response.statusText}]`)
×
132
    throw new Error(`Failed to activate OneRep profile: [${response.status}] [${response.statusText}]`)
×
133
  }
134
}
135

136
/**
137
 * @param {number} profileId
138
 * @returns {Promise<void>}
139
 */
140
export async function optoutProfile (profileId) {
141
  /**
142
   * See https://docs.onerep.com/#operation/optoutProfile
143
   */
144
  const response = await onerepFetch(`/profiles/${profileId}/optout`, {
×
145
    method: 'POST'
146
  })
147
  if (!response.ok) {
×
148
    log.info(`Failed to opt-out OneRep profile: [${response.status}] [${response.statusText}]`)
×
149
    throw new Error(`Failed to opt-out OneRep profile: [${response.status}] [${response.statusText}]`)
×
150
  }
151
}
152

153
/**
154
 * @typedef {object} CreateScanResponse
155
 * @property {number} id
156
 * @property {number} profile_id
157
 * @property {'in_progress'} status
158
 * @property {'manual'} reason
159
 * @property {import('../utils/parse.js').ISO8601DateString} created_at
160
 * @property {import('../utils/parse.js').ISO8601DateString} updated_at
161
 */
162

163
/**
164
 * @param {number} profileId
165
 * @returns {Promise<CreateScanResponse>}
166
 */
167
export async function createScan (profileId) {
168
  /**
169
   * See https://docs.onerep.com/#operation/createScan
170
   */
171
  const response = await onerepFetch(`/profiles/${profileId}/scans`, {
×
172
    method: 'POST'
173
  })
174
  if (!response.ok) {
×
175
    log.info(`Failed to create a scan: [${response.status}] [${response.statusText}]`)
×
176
    throw new Error(`Failed to create a scan: [${response.status}] [${response.statusText}]`)
×
177
  }
178
  return response.json()
×
179
}
180

181
/**
182
 * @typedef {{ current_page: number; from: number; last_page: number; per_page: number; to: number; total: number; }} OneRepMeta
183
 * @typedef {object} Scan
184
 * @property {number} id
185
 * @property {number} profile_id
186
 * @property {'in_progress' | 'finished'} status
187
 * @property {'initial' | 'monitoring' | 'manual'} reason
188
 * @typedef {{ meta: OneRepMeta, data: Scan[] }} ListScansResponse
189
 */
190

191
/**
192
 * @param {number} profileId
193
 * @param {Partial<{ page: number; per_page: number }>} [options]
194
 * @returns {Promise<ListScansResponse>}
195
 */
196
export async function listScans (profileId, options = {}) {
×
197
  const queryParams = new URLSearchParams()
×
198
  if (options.page) {
×
199
    queryParams.set('page', options.page.toString())
×
200
  }
201
  if (options.per_page) {
×
202
    queryParams.set('per_page', options.per_page.toString())
×
203
  }
204
  /**
205
   * See https://docs.onerep.com/#operation/getScans
206
   *
207
   * @type {any}
208
   */
209
  const response = await onerepFetch(`/profiles/${profileId}/scans?` + queryParams.toString(), {
×
210
    method: 'GET'
211
  })
212
  if (!response.ok) {
×
213
    log.info(`Failed to fetch scans: [${response.status}] [${response.statusText}]`)
×
214
    throw new Error(`Failed to fetch scans: [${response.status}] [${response.statusText}]`)
×
215
  }
216
  return response.json()
×
217
}
218

219
/**
220
 * @typedef {object} ScanResult
221
 * @property {number} id
222
 * @property {number} profile_id
223
 * @property {string} first_name
224
 * @property {string} last_name
225
 * @property {string} middle_name
226
 * @property {`${number}`} age
227
 * @property {Array<{ city: string; state: string; street: string; zip: string; }>} addresses
228
 * @property {string[]} phones
229
 * @property {string[]} emails
230
 * @property {string} data_broker
231
 * @property {import('../utils/parse.js').ISO8601DateString} created_at
232
 * @property {import('../utils/parse.js').ISO8601DateString} updated_at
233
 * @typedef {{ meta: OneRepMeta, data: ScanResult[] }} ListScanResultsResponse
234
 */
235

236
/**
237
 * @typedef {'new' | 'optout_in_progress' | 'waiting_for_verification' | 'removed'} RemovalStatus
238
 * @param {number} profileId
239
 * @param {Partial<{ page: number; per_page: number; status: RemovalStatus }>} [options]
240
 * @returns {Promise<ListScanResultsResponse>}
241
 */
242
export async function listScanResults (profileId, options = {}) {
×
243
  const queryParams = new URLSearchParams({ 'profile_id[]': profileId.toString() })
×
244
  if (options.page) {
×
245
    queryParams.set('page', options.page.toString())
×
246
  }
247
  if (options.per_page) {
×
248
    queryParams.set('per_page', options.per_page.toString())
×
249
  }
250
  if (options.status) {
×
251
    const statuses = Array.isArray(options.status) ? options.status : [options.status]
×
252
    statuses.forEach(status => {
×
253
      queryParams.append('status[]', status)
×
254
    })
255
  }
256
  /**
257
   * See https://docs.onerep.com/#operation/getScanResults
258
   *
259
   * @type {any}
260
   */
261
  const response = await onerepFetch('/scan-results/?' + queryParams.toString(), {
×
262
    method: 'GET'
263
  })
264
  if (!response.ok) {
×
265
    log.info(`Failed to fetch scan results: [${response.status}] [${response.statusText}]`)
×
266
    throw new Error(`Failed to fetch scan results: [${response.status}] [${response.statusText}]`)
×
267
  }
268
  return response.json()
×
269
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc