fiftyone.pipeline.did 4.5.40 → 4.5.41

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fiftyone.pipeline.did",
3
- "version": "4.5.40",
3
+ "version": "4.5.41",
4
4
  "description": "Strongly typed reader for the 51Did (51Degrees Identifier) value returned by the 51Degrees Cloud service. Parses the OWID envelope and exposes the usage, licence id, match key and terms document plus the identifier type. Compare match keys, never envelopes.",
5
5
  "keywords": [
6
6
  "51degrees",
@@ -27,7 +27,7 @@
27
27
  // FOD_CLOUD_API_URL as the client does.
28
28
 
29
29
  const {
30
- FodId, DidClient, ContextResult, DidNotSupportedError
30
+ FodId, DidClient, ContextResult, DidNotSupportedError, Usage, IdType
31
31
  } = require('../index');
32
32
 
33
33
  const resourceKey = process.env._51DEGREES_RESOURCE_KEY ||
@@ -92,4 +92,130 @@ live('DidClient against the cloud', () => {
92
92
  expect(result.statusCode).toBe(200);
93
93
  expect(result.context).toBe(ContextResult.UNREADABLE);
94
94
  });
95
+ // The versioned Model Terms for Marketing document a marketing 51Did is
96
+ // created under. Written out here rather than read from the package,
97
+ // because a test that asked the package what it expects would agree with
98
+ // itself whatever the package said. The literal is what a receiver has to
99
+ // be able to fetch.
100
+ const MODEL_TERMS_FOR_MARKETING_2 = 'https://m4ow.uk/mtm/2.txt';
101
+
102
+ // Each id.usage the service offers, the usage this package must answer
103
+ // with, and the terms the identifier must carry. A non-marketing
104
+ // identifier may not reach a demand source at all, so there is nothing
105
+ // for a receiver to agree to and it states no terms.
106
+ const usages = [
107
+ ['non-marketing', Usage.NON_MARKETING, null],
108
+ ['standard', Usage.STANDARD, MODEL_TERMS_FOR_MARKETING_2],
109
+ ['personalized', Usage.PERSONALIZED, MODEL_TERMS_FOR_MARKETING_2]
110
+ ];
111
+
112
+ // IAB TCF v2 consent strings, and the usage the service must derive from
113
+ // each without the caller stating one. The first grants all twelve
114
+ // purposes, the second the Appendix 1 standard set of 1, 2, 7, 8 and 11.
115
+ // These are the strings the cloud's own IabTcfElement tests use, repeated
116
+ // here rather than shared, for the same reason as the address above.
117
+ const consentStrings = [
118
+ ['AAAAAAAAAAAAAAAAAAAAAAAAAP_w', Usage.PERSONALIZED],
119
+ ['AAAAAAAAAAAAAAAAAAAAAAAAAMMg', Usage.STANDARD]
120
+ ];
121
+
122
+ // Asks the json endpoint for a 51Did with the given query fragment and
123
+ // returns every identifier it answered with. An empty array means the
124
+ // resource key is not entitled to that usage, which is reported by the
125
+ // caller rather than failed.
126
+ const identifiersFor = async (fragment) => {
127
+ const url = client.endpoint + 'json?resource=' +
128
+ encodeURIComponent(resourceKey) + '&' + fragment +
129
+ '&values=FODiD.IdProbGlobal&values=FODiD.IdProbLic';
130
+ const response = await fetch(url, {
131
+ headers: { 'User-Agent': 'fiftyone.pipeline.did tests' }
132
+ });
133
+ const body = await response.text();
134
+ if (response.status !== 200) {
135
+ throw new Error(`json answered HTTP ${response.status}: ${body}`);
136
+ }
137
+ const fodid = JSON.parse(body).fodid;
138
+ if (!fodid) {
139
+ return [];
140
+ }
141
+ return ['idprobglobal', 'idproblic']
142
+ .map((name) => fodid[name])
143
+ .filter((value) => typeof value === 'string' && value.length > 0)
144
+ .map((value) => FodId.fromBase64(value));
145
+ };
146
+
147
+ // Every field the flags byte carries, plus the terms, read through the
148
+ // accessors rather than by masking. The usage values are cumulative,
149
+ // being 001, 011 and 111, so a caller masking the byte for the
150
+ // non-marketing bit reads every marketing identifier as non-marketing.
151
+ // The label travels into the expectation so a failure names which
152
+ // identifier it was.
153
+ const assertAligned = (label, id, usage, terms, fromConsent) => {
154
+ expect({ label, usage: id.usage }).toEqual({ label, usage });
155
+ expect({ label, fromConsent: id.usageFromConsent })
156
+ .toEqual({ label, fromConsent });
157
+ expect({ label, terms: id.terms }).toEqual({ label, terms });
158
+ expect({ label, type: id.type })
159
+ .toEqual({ label, type: IdType.PROBABILISTIC });
160
+ };
161
+
162
+ test('every usage reads back the terms and flags the service wrote',
163
+ async () => {
164
+ let checked = 0;
165
+ for (const [name, usage, terms] of usages) {
166
+ const identifiers = await identifiersFor(
167
+ 'id.usage=' + encodeURIComponent(name));
168
+ if (identifiers.length === 0) {
169
+ console.warn(`id.usage=${name}: no identifier returned, so this ` +
170
+ 'key is not entitled to that usage.');
171
+ continue;
172
+ }
173
+ identifiers.forEach((id, index) => {
174
+ assertAligned(`${name}[${index}]`, id, usage, terms, false);
175
+ });
176
+ // Only a marketing usage carries a terms address, so only a
177
+ // marketing identifier shows that the service wrote the byte. A
178
+ // non-marketing one states no terms either way, which is the same
179
+ // answer a service predating the Terms release would give.
180
+ if (terms !== null) {
181
+ checked += identifiers.length;
182
+ }
183
+ }
184
+ if (checked === 0) {
185
+ console.warn('NOTHING PROVEN: this resource key returned no ' +
186
+ 'marketing 51Did, so no terms address was read. Use a key ' +
187
+ 'entitled to the standard or personalized usage.');
188
+ } else {
189
+ console.log(`Terms checked on ${checked} marketing identifier(s).`);
190
+ }
191
+ });
192
+
193
+ test('a consent string sets the usage-from-consent bit', async () => {
194
+ let proven = 0;
195
+ for (const [tcString, usage] of consentStrings) {
196
+ // No id.usage is sent. A stated usage wins over a consent string, so
197
+ // sending one would leave the bit clear and prove the opposite.
198
+ const identifiers = await identifiersFor(
199
+ 'tcstring=' + encodeURIComponent(tcString));
200
+ if (identifiers.length === 0) {
201
+ console.warn(`consent string granting ${usage}: no identifier ` +
202
+ 'returned, so this key is not entitled to that marketing usage.');
203
+ continue;
204
+ }
205
+ identifiers.forEach((id, index) => {
206
+ // A consent string granting a marketing usage produces a marketing
207
+ // identifier, so the terms travel with it too.
208
+ assertAligned(`consent/${usage}[${index}]`, id, usage,
209
+ MODEL_TERMS_FOR_MARKETING_2, true);
210
+ });
211
+ proven += identifiers.length;
212
+ }
213
+ if (proven === 0) {
214
+ console.warn('NOTHING PROVEN: this resource key returned no ' +
215
+ 'identifier for either consent string, so the usage-from-consent ' +
216
+ 'bit was never read.');
217
+ } else {
218
+ console.log(`Usage-from-consent read on ${proven} identifier(s).`);
219
+ }
220
+ });
95
221
  });