@financial-times/cp-content-pipeline-schema 3.13.0 → 3.14.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/CHANGELOG.md +22 -0
- package/lib/datasources/origami-image.js +2 -2
- package/lib/datasources/origami-image.js.map +1 -1
- package/lib/datasources/url-management.js +45 -7
- package/lib/datasources/url-management.js.map +1 -1
- package/lib/datasources/url-management.test.js +97 -0
- package/lib/datasources/url-management.test.js.map +1 -1
- package/lib/model/Concept.js +7 -1
- package/lib/model/Concept.js.map +1 -1
- package/lib/model/Content.js +6 -1
- package/lib/model/Content.js.map +1 -1
- package/lib/model/Image.test.js +3 -3
- package/lib/model/RichText.test.js +9 -9
- package/lib/resolvers/content-tree/bodyXMLToTree.test.js +176 -176
- package/package.json +1 -1
- package/src/datasources/origami-image.ts +2 -2
- package/src/datasources/url-management.test.ts +126 -0
- package/src/datasources/url-management.ts +48 -8
- package/src/model/Concept.ts +7 -1
- package/src/model/Content.ts +6 -1
- package/src/model/Image.test.ts +3 -3
- package/src/model/RichText.test.ts +9 -9
- package/src/model/__snapshots__/Byline.test.ts.snap +99 -99
- package/src/model/__snapshots__/RichText.test.ts.snap +305 -305
- package/src/resolvers/content-tree/bodyXMLToTree.test.ts +176 -176
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -9,6 +9,7 @@ import nUrlManagementApiReadClient from '@financial-times/n-url-management-api-r
|
|
|
9
9
|
import type { QueryContext } from '..'
|
|
10
10
|
import { InMemoryLRUCache } from '@apollo/utils.keyvaluecache'
|
|
11
11
|
import { jest } from '@jest/globals'
|
|
12
|
+
import { BaseError, HttpError } from '@dotcom-reliability-kit/errors'
|
|
12
13
|
|
|
13
14
|
const getMock = nUrlManagementApiReadClient.get as jest.Mock<
|
|
14
15
|
typeof import('@financial-times/n-url-management-api-read-client')['get']
|
|
@@ -95,4 +96,129 @@ describe('URL management data source', () => {
|
|
|
95
96
|
expect(different).toEqual('different.url.vanity')
|
|
96
97
|
expect(nUrlManagementApiReadClient.get).toHaveBeenCalledTimes(2)
|
|
97
98
|
})
|
|
99
|
+
|
|
100
|
+
describe('error handling', () => {
|
|
101
|
+
it('wraps 4xx errors in HttpError', async () => {
|
|
102
|
+
getMock.mockRejectedValueOnce(
|
|
103
|
+
new HttpError({
|
|
104
|
+
statusCode: 400,
|
|
105
|
+
code: 'UnrecognisedClientException',
|
|
106
|
+
})
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
const datasource = new URLManagementDataSource({
|
|
110
|
+
context,
|
|
111
|
+
cache: new InMemoryLRUCache(),
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
await expect(datasource.get('original.url')).rejects
|
|
115
|
+
.toMatchInlineSnapshot(`
|
|
116
|
+
HttpError: "Internal Server Error" {
|
|
117
|
+
"cause": "HttpError: "Bad Request" {
|
|
118
|
+
"cause": null,
|
|
119
|
+
"code": "UNRECOGNISEDCLIENTEXCEPTION",
|
|
120
|
+
"data": {},
|
|
121
|
+
}",
|
|
122
|
+
"code": "URL_MANAGEMENT_CLIENT_ERROR",
|
|
123
|
+
"data": {
|
|
124
|
+
"upstreamStatusCode": 400,
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
`)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('wraps 5xx errors in UpstreamError', async () => {
|
|
131
|
+
getMock.mockRejectedValueOnce(
|
|
132
|
+
new HttpError({
|
|
133
|
+
statusCode: 500,
|
|
134
|
+
code: 'InternalServerException',
|
|
135
|
+
})
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
const datasource = new URLManagementDataSource({
|
|
139
|
+
context,
|
|
140
|
+
cache: new InMemoryLRUCache(),
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
await expect(datasource.get('original.url')).rejects
|
|
144
|
+
.toMatchInlineSnapshot(`
|
|
145
|
+
UpstreamServiceError: "Bad Gateway" {
|
|
146
|
+
"cause": "HttpError: "Internal Server Error" {
|
|
147
|
+
"cause": null,
|
|
148
|
+
"code": "INTERNALSERVEREXCEPTION",
|
|
149
|
+
"data": {},
|
|
150
|
+
}",
|
|
151
|
+
"code": "URL_MANAGEMENT_SERVER_ERROR",
|
|
152
|
+
"data": {
|
|
153
|
+
"upstreamStatusCode": 500,
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
`)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('wraps other HTTP errors in HttpError', async () => {
|
|
160
|
+
const error = new BaseError({
|
|
161
|
+
code: 'SomethingElseException',
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
Object.assign(error, {
|
|
165
|
+
// HttpError normalises status codes to the 400-599 range
|
|
166
|
+
statusCode: 137,
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
getMock.mockRejectedValueOnce(error)
|
|
170
|
+
|
|
171
|
+
const datasource = new URLManagementDataSource({
|
|
172
|
+
context,
|
|
173
|
+
cache: new InMemoryLRUCache(),
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
await expect(datasource.get('original.url')).rejects
|
|
177
|
+
.toMatchInlineSnapshot(`
|
|
178
|
+
HttpError: "Internal Server Error" {
|
|
179
|
+
"cause": "BaseError: "An error occurred" {
|
|
180
|
+
"cause": null,
|
|
181
|
+
"code": "SOMETHINGELSEEXCEPTION",
|
|
182
|
+
"data": {},
|
|
183
|
+
}",
|
|
184
|
+
"code": "URL_MANAGEMENT_UNKNOWN_ERROR",
|
|
185
|
+
"data": {
|
|
186
|
+
"upstreamStatusCode": 137,
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
`)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('rethrows unknown errors', async () => {
|
|
193
|
+
getMock.mockRejectedValueOnce('not an error')
|
|
194
|
+
|
|
195
|
+
const datasource = new URLManagementDataSource({
|
|
196
|
+
context,
|
|
197
|
+
cache: new InMemoryLRUCache(),
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
await expect(
|
|
201
|
+
datasource.get('original.url')
|
|
202
|
+
).rejects.toMatchInlineSnapshot(`"not an error"`)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it("doesn't cache error responses", async () => {
|
|
206
|
+
getMock.mockRejectedValueOnce(new Error())
|
|
207
|
+
|
|
208
|
+
const datasource = new URLManagementDataSource({
|
|
209
|
+
context,
|
|
210
|
+
cache: new InMemoryLRUCache(),
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
await expect(datasource.get('original.url')).rejects.toThrow()
|
|
214
|
+
|
|
215
|
+
getMock.mockImplementation((fromURL) =>
|
|
216
|
+
Promise.resolve({ fromURL, toURL: `${fromURL}.vanity`, code: 0 })
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
await expect(datasource.get('original.url')).resolves.toEqual(
|
|
220
|
+
`original.url.vanity`
|
|
221
|
+
)
|
|
222
|
+
})
|
|
223
|
+
})
|
|
98
224
|
})
|
|
@@ -7,6 +7,8 @@ import nUrlManagementApiReadClient from '@financial-times/n-url-management-api-r
|
|
|
7
7
|
import type { ResolvedVanityURL } from '@financial-times/n-url-management-api-read-client'
|
|
8
8
|
import { QueryContext } from '..'
|
|
9
9
|
import { BaseDataSource, BaseDataSourceOptions } from './base'
|
|
10
|
+
import { HttpError, UpstreamServiceError } from '@dotcom-reliability-kit/errors'
|
|
11
|
+
import isError from '../helpers/isError'
|
|
10
12
|
|
|
11
13
|
const CACHE_PREFIX = 'nurlmgmtapi:'
|
|
12
14
|
const DEFAULT_TTL_IN_SECONDS = 60 * 60 // one hour
|
|
@@ -53,16 +55,54 @@ export class URLManagementDataSource implements BaseDataSource {
|
|
|
53
55
|
return fromDynamoDB.toURL
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
try {
|
|
59
|
+
let promise = this.memoizedResults.get(fromURL)
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
if (promise) {
|
|
62
|
+
return await promise
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
promise = getVanity()
|
|
66
|
+
this.memoizedResults.set(fromURL, promise)
|
|
67
|
+
|
|
68
|
+
this.calls.push(fromURL)
|
|
69
|
+
return await promise
|
|
70
|
+
} catch (error) {
|
|
71
|
+
this.memoizedResults.delete(fromURL)
|
|
72
|
+
await this.cache.delete(fromURL)
|
|
61
73
|
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
if (
|
|
75
|
+
isError(error) &&
|
|
76
|
+
'statusCode' in error &&
|
|
77
|
+
typeof error.statusCode === 'number'
|
|
78
|
+
) {
|
|
79
|
+
const baseErrorOptions = {
|
|
80
|
+
cause: error,
|
|
81
|
+
relatesToSystems: ['next-url-management-db'],
|
|
82
|
+
upstreamStatusCode: error.statusCode,
|
|
83
|
+
}
|
|
64
84
|
|
|
65
|
-
|
|
66
|
-
|
|
85
|
+
if (error.statusCode >= 400 && error.statusCode < 500) {
|
|
86
|
+
throw new HttpError({
|
|
87
|
+
code: 'URL_MANAGEMENT_CLIENT_ERROR',
|
|
88
|
+
statusCode: 500,
|
|
89
|
+
...baseErrorOptions,
|
|
90
|
+
})
|
|
91
|
+
} else if (error.statusCode >= 500 && error.statusCode < 600) {
|
|
92
|
+
throw new UpstreamServiceError({
|
|
93
|
+
code: 'URL_MANAGEMENT_SERVER_ERROR',
|
|
94
|
+
...baseErrorOptions,
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
throw new HttpError({
|
|
99
|
+
code: 'URL_MANAGEMENT_UNKNOWN_ERROR',
|
|
100
|
+
statusCode: 500,
|
|
101
|
+
...baseErrorOptions,
|
|
102
|
+
})
|
|
103
|
+
} else {
|
|
104
|
+
throw error
|
|
105
|
+
}
|
|
106
|
+
}
|
|
67
107
|
}
|
|
68
108
|
}
|
package/src/model/Concept.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { QueryContext } from '..'
|
|
|
3
3
|
import { uuidFromUrl } from '../helpers/metadata'
|
|
4
4
|
import isError from '../helpers/isError'
|
|
5
5
|
import conceptIds from '@financial-times/n-concept-ids'
|
|
6
|
+
import { OperationalError } from '@dotcom-reliability-kit/errors'
|
|
6
7
|
|
|
7
8
|
const CAPI_ID_PREFIX = /^https?:\/\/(?:www|api)\.ft\.com\/things?\//
|
|
8
9
|
const BASE_URL = 'https://www.ft.com/stream/'
|
|
@@ -155,7 +156,12 @@ export class Concept {
|
|
|
155
156
|
if (isError(error)) {
|
|
156
157
|
this.context.logger.warn({
|
|
157
158
|
event: 'RECOVERABLE_ERROR',
|
|
158
|
-
error
|
|
159
|
+
error: new OperationalError({
|
|
160
|
+
code: 'CONCEPT_VANITY_ERROR',
|
|
161
|
+
message: `Could not get vanity URL for concept. This means we'll be displaying the underlying URL instead of the vanity, which will cause additional redirects.`,
|
|
162
|
+
uuid: this.id(),
|
|
163
|
+
cause: error,
|
|
164
|
+
}),
|
|
159
165
|
})
|
|
160
166
|
}
|
|
161
167
|
}
|
package/src/model/Content.ts
CHANGED
|
@@ -252,7 +252,12 @@ export class Content {
|
|
|
252
252
|
if (isError(error)) {
|
|
253
253
|
this.context.logger.warn({
|
|
254
254
|
event: 'RECOVERABLE_ERROR',
|
|
255
|
-
error
|
|
255
|
+
error: new OperationalError({
|
|
256
|
+
code: 'CONTENT_VANITY_ERROR',
|
|
257
|
+
message: `Could not get vanity URL for content. This means we'll be displaying the underlying URL instead of the vanity, which will cause additional redirects.`,
|
|
258
|
+
uuid: this.id(),
|
|
259
|
+
cause: error,
|
|
260
|
+
}),
|
|
256
261
|
})
|
|
257
262
|
}
|
|
258
263
|
}
|
package/src/model/Image.test.ts
CHANGED
|
@@ -148,13 +148,13 @@ describe('Image', () => {
|
|
|
148
148
|
})
|
|
149
149
|
it('returns an image service URL with the requested width and max DPR 2', () => {
|
|
150
150
|
expect(sourceSet).toMatchInlineSnapshot(`
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
[
|
|
152
|
+
{
|
|
153
153
|
"dpr": 1,
|
|
154
154
|
"url": "https://images.ft.com/v3/image/raw/ftcms%3A00000000-0000-0000-0000-000000000001?source=image-test&fit=scale-down&quality=highest&width=3000&dpr=1",
|
|
155
155
|
"width": 3000,
|
|
156
156
|
},
|
|
157
|
-
|
|
157
|
+
{
|
|
158
158
|
"dpr": 2,
|
|
159
159
|
"url": "https://images.ft.com/v3/image/raw/ftcms%3A00000000-0000-0000-0000-000000000001?source=image-test&fit=scale-down&quality=highest&width=3000&dpr=2",
|
|
160
160
|
"width": 3000,
|
|
@@ -15,12 +15,12 @@ describe('RichText resolver', () => {
|
|
|
15
15
|
'The UK’s first ID Buzz campervan is available to rent. On a test-drive in Sussex, it charms both passengers and passers-by'
|
|
16
16
|
)
|
|
17
17
|
expect(await model.structured()).toMatchInlineSnapshot(`
|
|
18
|
-
|
|
19
|
-
"references":
|
|
18
|
+
{
|
|
19
|
+
"references": [],
|
|
20
20
|
"text": "",
|
|
21
|
-
"tree":
|
|
22
|
-
"children":
|
|
23
|
-
|
|
21
|
+
"tree": {
|
|
22
|
+
"children": [
|
|
23
|
+
{
|
|
24
24
|
"type": "text",
|
|
25
25
|
"value": "The UK’s first ID Buzz campervan is available to rent. On a test-drive in Sussex, it charms both passengers and passers-by",
|
|
26
26
|
},
|
|
@@ -35,11 +35,11 @@ describe('RichText resolver', () => {
|
|
|
35
35
|
it('should handle nulls', async () => {
|
|
36
36
|
const model = new RichText('standfirst', null)
|
|
37
37
|
expect(await model.structured()).toMatchInlineSnapshot(`
|
|
38
|
-
|
|
39
|
-
"references":
|
|
38
|
+
{
|
|
39
|
+
"references": [],
|
|
40
40
|
"text": "",
|
|
41
|
-
"tree":
|
|
42
|
-
"children":
|
|
41
|
+
"tree": {
|
|
42
|
+
"children": [],
|
|
43
43
|
"type": "body",
|
|
44
44
|
"version": 1,
|
|
45
45
|
},
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
2
|
|
|
3
3
|
exports[`byline transformation adds a link around a single known author 1`] = `
|
|
4
|
-
|
|
5
|
-
"references":
|
|
6
|
-
|
|
4
|
+
{
|
|
5
|
+
"references": [
|
|
6
|
+
{
|
|
7
7
|
"contentApiData": undefined,
|
|
8
|
-
"reference":
|
|
9
|
-
"children":
|
|
10
|
-
|
|
8
|
+
"reference": {
|
|
9
|
+
"children": [
|
|
10
|
+
{
|
|
11
11
|
"type": "text",
|
|
12
12
|
"value": "Chris Giles",
|
|
13
13
|
},
|
|
14
14
|
],
|
|
15
|
-
"data":
|
|
15
|
+
"data": {
|
|
16
16
|
"referenceIndex": 0,
|
|
17
17
|
},
|
|
18
18
|
"id": "1d556016-ad16-4fe7-8724-42b3fb15ad28",
|
|
@@ -20,22 +20,22 @@ Object {
|
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
22
|
],
|
|
23
|
-
"tree":
|
|
24
|
-
"children":
|
|
25
|
-
|
|
26
|
-
"children":
|
|
27
|
-
|
|
23
|
+
"tree": {
|
|
24
|
+
"children": [
|
|
25
|
+
{
|
|
26
|
+
"children": [
|
|
27
|
+
{
|
|
28
28
|
"type": "text",
|
|
29
29
|
"value": "Chris Giles",
|
|
30
30
|
},
|
|
31
31
|
],
|
|
32
|
-
"data":
|
|
32
|
+
"data": {
|
|
33
33
|
"referenceIndex": 0,
|
|
34
34
|
},
|
|
35
35
|
"id": "1d556016-ad16-4fe7-8724-42b3fb15ad28",
|
|
36
36
|
"type": "author",
|
|
37
37
|
},
|
|
38
|
-
|
|
38
|
+
{
|
|
39
39
|
"type": "text",
|
|
40
40
|
"value": " in London",
|
|
41
41
|
},
|
|
@@ -46,34 +46,34 @@ Object {
|
|
|
46
46
|
`;
|
|
47
47
|
|
|
48
48
|
exports[`byline transformation adds links around multiple known authors 1`] = `
|
|
49
|
-
|
|
50
|
-
"references":
|
|
51
|
-
|
|
49
|
+
{
|
|
50
|
+
"references": [
|
|
51
|
+
{
|
|
52
52
|
"contentApiData": undefined,
|
|
53
|
-
"reference":
|
|
54
|
-
"children":
|
|
55
|
-
|
|
53
|
+
"reference": {
|
|
54
|
+
"children": [
|
|
55
|
+
{
|
|
56
56
|
"type": "text",
|
|
57
57
|
"value": "Chris Giles",
|
|
58
58
|
},
|
|
59
59
|
],
|
|
60
|
-
"data":
|
|
60
|
+
"data": {
|
|
61
61
|
"referenceIndex": 0,
|
|
62
62
|
},
|
|
63
63
|
"id": "1d556016-ad16-4fe7-8724-42b3fb15ad28",
|
|
64
64
|
"type": "author",
|
|
65
65
|
},
|
|
66
66
|
},
|
|
67
|
-
|
|
67
|
+
{
|
|
68
68
|
"contentApiData": undefined,
|
|
69
|
-
"reference":
|
|
70
|
-
"children":
|
|
71
|
-
|
|
69
|
+
"reference": {
|
|
70
|
+
"children": [
|
|
71
|
+
{
|
|
72
72
|
"type": "text",
|
|
73
73
|
"value": "Martin Wolf",
|
|
74
74
|
},
|
|
75
75
|
],
|
|
76
|
-
"data":
|
|
76
|
+
"data": {
|
|
77
77
|
"referenceIndex": 1,
|
|
78
78
|
},
|
|
79
79
|
"id": "7c1e1e72-57ae-4461-862a-f8d24dd42e22",
|
|
@@ -81,39 +81,39 @@ Object {
|
|
|
81
81
|
},
|
|
82
82
|
},
|
|
83
83
|
],
|
|
84
|
-
"tree":
|
|
85
|
-
"children":
|
|
86
|
-
|
|
87
|
-
"children":
|
|
88
|
-
|
|
84
|
+
"tree": {
|
|
85
|
+
"children": [
|
|
86
|
+
{
|
|
87
|
+
"children": [
|
|
88
|
+
{
|
|
89
89
|
"type": "text",
|
|
90
90
|
"value": "Chris Giles",
|
|
91
91
|
},
|
|
92
92
|
],
|
|
93
|
-
"data":
|
|
93
|
+
"data": {
|
|
94
94
|
"referenceIndex": 0,
|
|
95
95
|
},
|
|
96
96
|
"id": "1d556016-ad16-4fe7-8724-42b3fb15ad28",
|
|
97
97
|
"type": "author",
|
|
98
98
|
},
|
|
99
|
-
|
|
99
|
+
{
|
|
100
100
|
"type": "text",
|
|
101
101
|
"value": " and ",
|
|
102
102
|
},
|
|
103
|
-
|
|
104
|
-
"children":
|
|
105
|
-
|
|
103
|
+
{
|
|
104
|
+
"children": [
|
|
105
|
+
{
|
|
106
106
|
"type": "text",
|
|
107
107
|
"value": "Martin Wolf",
|
|
108
108
|
},
|
|
109
109
|
],
|
|
110
|
-
"data":
|
|
110
|
+
"data": {
|
|
111
111
|
"referenceIndex": 1,
|
|
112
112
|
},
|
|
113
113
|
"id": "7c1e1e72-57ae-4461-862a-f8d24dd42e22",
|
|
114
114
|
"type": "author",
|
|
115
115
|
},
|
|
116
|
-
|
|
116
|
+
{
|
|
117
117
|
"type": "text",
|
|
118
118
|
"value": " in London",
|
|
119
119
|
},
|
|
@@ -124,18 +124,18 @@ Object {
|
|
|
124
124
|
`;
|
|
125
125
|
|
|
126
126
|
exports[`byline transformation converts straight apostrophes in byline 1`] = `
|
|
127
|
-
|
|
128
|
-
"references":
|
|
129
|
-
|
|
127
|
+
{
|
|
128
|
+
"references": [
|
|
129
|
+
{
|
|
130
130
|
"contentApiData": undefined,
|
|
131
|
-
"reference":
|
|
132
|
-
"children":
|
|
133
|
-
|
|
131
|
+
"reference": {
|
|
132
|
+
"children": [
|
|
133
|
+
{
|
|
134
134
|
"type": "text",
|
|
135
135
|
"value": "Sarah O’Connor",
|
|
136
136
|
},
|
|
137
137
|
],
|
|
138
|
-
"data":
|
|
138
|
+
"data": {
|
|
139
139
|
"referenceIndex": 0,
|
|
140
140
|
},
|
|
141
141
|
"id": "1d556016-ad16-4fe7-8724-eeeeeeeeeeee",
|
|
@@ -143,22 +143,22 @@ Object {
|
|
|
143
143
|
},
|
|
144
144
|
},
|
|
145
145
|
],
|
|
146
|
-
"tree":
|
|
147
|
-
"children":
|
|
148
|
-
|
|
149
|
-
"children":
|
|
150
|
-
|
|
146
|
+
"tree": {
|
|
147
|
+
"children": [
|
|
148
|
+
{
|
|
149
|
+
"children": [
|
|
150
|
+
{
|
|
151
151
|
"type": "text",
|
|
152
152
|
"value": "Sarah O’Connor",
|
|
153
153
|
},
|
|
154
154
|
],
|
|
155
|
-
"data":
|
|
155
|
+
"data": {
|
|
156
156
|
"referenceIndex": 0,
|
|
157
157
|
},
|
|
158
158
|
"id": "1d556016-ad16-4fe7-8724-eeeeeeeeeeee",
|
|
159
159
|
"type": "author",
|
|
160
160
|
},
|
|
161
|
-
|
|
161
|
+
{
|
|
162
162
|
"type": "text",
|
|
163
163
|
"value": " in Bali",
|
|
164
164
|
},
|
|
@@ -169,11 +169,11 @@ Object {
|
|
|
169
169
|
`;
|
|
170
170
|
|
|
171
171
|
exports[`byline transformation handles bylines without any matching authors 1`] = `
|
|
172
|
-
|
|
173
|
-
"references":
|
|
174
|
-
"tree":
|
|
175
|
-
"children":
|
|
176
|
-
|
|
172
|
+
{
|
|
173
|
+
"references": [],
|
|
174
|
+
"tree": {
|
|
175
|
+
"children": [
|
|
176
|
+
{
|
|
177
177
|
"type": "text",
|
|
178
178
|
"value": "Chris Giles and Nick Ramsbottom in London",
|
|
179
179
|
},
|
|
@@ -184,18 +184,18 @@ Object {
|
|
|
184
184
|
`;
|
|
185
185
|
|
|
186
186
|
exports[`byline transformation ignores extra authors in annotations array 1`] = `
|
|
187
|
-
|
|
188
|
-
"references":
|
|
189
|
-
|
|
187
|
+
{
|
|
188
|
+
"references": [
|
|
189
|
+
{
|
|
190
190
|
"contentApiData": undefined,
|
|
191
|
-
"reference":
|
|
192
|
-
"children":
|
|
193
|
-
|
|
191
|
+
"reference": {
|
|
192
|
+
"children": [
|
|
193
|
+
{
|
|
194
194
|
"type": "text",
|
|
195
195
|
"value": "Martin Wolf",
|
|
196
196
|
},
|
|
197
197
|
],
|
|
198
|
-
"data":
|
|
198
|
+
"data": {
|
|
199
199
|
"referenceIndex": 0,
|
|
200
200
|
},
|
|
201
201
|
"id": "7c1e1e72-57ae-4461-862a-f8d24dd42e22",
|
|
@@ -203,22 +203,22 @@ Object {
|
|
|
203
203
|
},
|
|
204
204
|
},
|
|
205
205
|
],
|
|
206
|
-
"tree":
|
|
207
|
-
"children":
|
|
208
|
-
|
|
209
|
-
"children":
|
|
210
|
-
|
|
206
|
+
"tree": {
|
|
207
|
+
"children": [
|
|
208
|
+
{
|
|
209
|
+
"children": [
|
|
210
|
+
{
|
|
211
211
|
"type": "text",
|
|
212
212
|
"value": "Martin Wolf",
|
|
213
213
|
},
|
|
214
214
|
],
|
|
215
|
-
"data":
|
|
215
|
+
"data": {
|
|
216
216
|
"referenceIndex": 0,
|
|
217
217
|
},
|
|
218
218
|
"id": "7c1e1e72-57ae-4461-862a-f8d24dd42e22",
|
|
219
219
|
"type": "author",
|
|
220
220
|
},
|
|
221
|
-
|
|
221
|
+
{
|
|
222
222
|
"type": "text",
|
|
223
223
|
"value": " in London",
|
|
224
224
|
},
|
|
@@ -229,18 +229,18 @@ Object {
|
|
|
229
229
|
`;
|
|
230
230
|
|
|
231
231
|
exports[`byline transformation ignores unknown authors in byline text 1`] = `
|
|
232
|
-
|
|
233
|
-
"references":
|
|
234
|
-
|
|
232
|
+
{
|
|
233
|
+
"references": [
|
|
234
|
+
{
|
|
235
235
|
"contentApiData": undefined,
|
|
236
|
-
"reference":
|
|
237
|
-
"children":
|
|
238
|
-
|
|
236
|
+
"reference": {
|
|
237
|
+
"children": [
|
|
238
|
+
{
|
|
239
239
|
"type": "text",
|
|
240
240
|
"value": "Chris Giles",
|
|
241
241
|
},
|
|
242
242
|
],
|
|
243
|
-
"data":
|
|
243
|
+
"data": {
|
|
244
244
|
"referenceIndex": 0,
|
|
245
245
|
},
|
|
246
246
|
"id": "1d556016-ad16-4fe7-8724-42b3fb15ad28",
|
|
@@ -248,22 +248,22 @@ Object {
|
|
|
248
248
|
},
|
|
249
249
|
},
|
|
250
250
|
],
|
|
251
|
-
"tree":
|
|
252
|
-
"children":
|
|
253
|
-
|
|
254
|
-
"children":
|
|
255
|
-
|
|
251
|
+
"tree": {
|
|
252
|
+
"children": [
|
|
253
|
+
{
|
|
254
|
+
"children": [
|
|
255
|
+
{
|
|
256
256
|
"type": "text",
|
|
257
257
|
"value": "Chris Giles",
|
|
258
258
|
},
|
|
259
259
|
],
|
|
260
|
-
"data":
|
|
260
|
+
"data": {
|
|
261
261
|
"referenceIndex": 0,
|
|
262
262
|
},
|
|
263
263
|
"id": "1d556016-ad16-4fe7-8724-42b3fb15ad28",
|
|
264
264
|
"type": "author",
|
|
265
265
|
},
|
|
266
|
-
|
|
266
|
+
{
|
|
267
267
|
"type": "text",
|
|
268
268
|
"value": " and Nick Ramsbottom in London",
|
|
269
269
|
},
|
|
@@ -274,18 +274,18 @@ Object {
|
|
|
274
274
|
`;
|
|
275
275
|
|
|
276
276
|
exports[`byline transformation leaves curly apostrophes in byline 1`] = `
|
|
277
|
-
|
|
278
|
-
"references":
|
|
279
|
-
|
|
277
|
+
{
|
|
278
|
+
"references": [
|
|
279
|
+
{
|
|
280
280
|
"contentApiData": undefined,
|
|
281
|
-
"reference":
|
|
282
|
-
"children":
|
|
283
|
-
|
|
281
|
+
"reference": {
|
|
282
|
+
"children": [
|
|
283
|
+
{
|
|
284
284
|
"type": "text",
|
|
285
285
|
"value": "Sarah O’Connor",
|
|
286
286
|
},
|
|
287
287
|
],
|
|
288
|
-
"data":
|
|
288
|
+
"data": {
|
|
289
289
|
"referenceIndex": 0,
|
|
290
290
|
},
|
|
291
291
|
"id": "1d556016-ad16-4fe7-8724-eeeeeeeeeeee",
|
|
@@ -293,22 +293,22 @@ Object {
|
|
|
293
293
|
},
|
|
294
294
|
},
|
|
295
295
|
],
|
|
296
|
-
"tree":
|
|
297
|
-
"children":
|
|
298
|
-
|
|
299
|
-
"children":
|
|
300
|
-
|
|
296
|
+
"tree": {
|
|
297
|
+
"children": [
|
|
298
|
+
{
|
|
299
|
+
"children": [
|
|
300
|
+
{
|
|
301
301
|
"type": "text",
|
|
302
302
|
"value": "Sarah O’Connor",
|
|
303
303
|
},
|
|
304
304
|
],
|
|
305
|
-
"data":
|
|
305
|
+
"data": {
|
|
306
306
|
"referenceIndex": 0,
|
|
307
307
|
},
|
|
308
308
|
"id": "1d556016-ad16-4fe7-8724-eeeeeeeeeeee",
|
|
309
309
|
"type": "author",
|
|
310
310
|
},
|
|
311
|
-
|
|
311
|
+
{
|
|
312
312
|
"type": "text",
|
|
313
313
|
"value": " in Bali",
|
|
314
314
|
},
|