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

mozilla / blurts-server / #13269

pending completion
#13269

push

circleci

Vinnl
fixup! Store exposure scan data at OneRep

282 of 1736 branches covered (16.24%)

Branch coverage included in aggregate %.

7 of 7 new or added lines in 1 file covered. (100.0%)

959 of 4615 relevant lines covered (20.78%)

1.73 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
 * @typedef {{ current_page: number; from: number; last_page: number; per_page: number; to: number; total: number; }} OneRepMeta
138
 * @typedef {object} Scan
139
 * @property {number} id
140
 * @property {number} profile_id
141
 * @property {'in_progress' | 'finished'} status
142
 * @property {'initial' | 'monitoring' | 'manual'} reason
143
 * @typedef {{ meta: OneRepMeta, data: Scan[] }} ListScansResponse
144
 */
145

146
/**
147
 * @param {number} profileId
148
 * @param {Partial<{ page: number; per_page: number }>} [options]
149
 * @returns {Promise<ListScansResponse>}
150
 */
151
export async function listScans (profileId, options = {}) {
×
152
  const queryParams = new URLSearchParams()
×
153
  if (options.page) {
×
154
    queryParams.set('page', options.page.toString())
×
155
  }
156
  if (options.per_page) {
×
157
    queryParams.set('per_page', options.per_page.toString())
×
158
  }
159
  /**
160
   * See https://docs.onerep.com/#operation/getScans
161
   *
162
   * @type {any}
163
   */
164
  const response = await onerepFetch(`/profiles/${profileId}/scans?` + queryParams.toString(), {
×
165
    method: 'GET'
166
  })
167
  if (!response.ok) {
×
168
    log.info(`Failed to fetch scans: [${response.status}] [${response.statusText}]`)
×
169
    throw new Error(`Failed to fetch scans: [${response.status}] [${response.statusText}]`)
×
170
  }
171
  return response.json()
×
172
}
173

174
/**
175
 * @typedef {object} ScanResult
176
 * @property {number} id
177
 * @property {number} profile_id
178
 * @property {string} first_name
179
 * @property {string} last_name
180
 * @property {string} middle_name
181
 * @property {`${number}`} age
182
 * @property {Array<{ city: string; state: string; street: string; zip: string; }>} addresses
183
 * @property {string[]} phones
184
 * @property {string[]} emails
185
 * @property {string} data_broker
186
 * @property {import('../utils/parse.js').ISO8601DateString} created_at
187
 * @property {import('../utils/parse.js').ISO8601DateString} updated_at
188
 * @typedef {{ meta: OneRepMeta, data: ScanResult[] }} ListScanResultsResponse
189
 */
190

191
/**
192
 * @typedef {'new' | 'optout_in_progress' | 'waiting_for_verification' | 'removed'} RemovalStatus
193
 * @param {number} profileId
194
 * @param {Partial<{ page: number; per_page: number; status: RemovalStatus }>} [options]
195
 * @returns {Promise<ListScanResultsResponse>}
196
 */
197
export async function listScanResults (profileId, options = {}) {
×
198
  const queryParams = new URLSearchParams({ 'profile_id[]': profileId.toString() })
×
199
  if (options.page) {
×
200
    queryParams.set('page', options.page.toString())
×
201
  }
202
  if (options.per_page) {
×
203
    queryParams.set('per_page', options.per_page.toString())
×
204
  }
205
  if (options.status) {
×
206
    const statuses = Array.isArray(options.status) ? options.status : [options.status]
×
207
    statuses.forEach(status => {
×
208
      queryParams.append('status[]', status)
×
209
    })
210
  }
211
  /**
212
   * See https://docs.onerep.com/#operation/getScanResults
213
   *
214
   * @type {any}
215
   */
216
  const response = await onerepFetch('/scan-results/?' + queryParams.toString(), {
×
217
    method: 'GET'
218
  })
219
  if (!response.ok) {
×
220
    log.info(`Failed to fetch scan results: [${response.status}] [${response.statusText}]`)
×
221
    throw new Error(`Failed to fetch scan results: [${response.status}] [${response.statusText}]`)
×
222
  }
223
  return response.json()
×
224
}
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