Skip to content

Commit 06c8a4a

Browse files
authored
Merge pull request #24 from mqchau/handle202
Handle response with status code 202 with potential background job details
2 parents 0b28e96 + 7e306cc commit 06c8a4a

File tree

5 files changed

+119
-7
lines changed

5 files changed

+119
-7
lines changed

src/deferred-action-callback.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface DeferredActionCallback {
2+
(responseObject: any): void
3+
}

src/model.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { cloneDeep } from "./util/clonedeep"
4040
import { nonenumerable } from "./util/decorators"
4141
import { IncludeScopeHash } from "./util/include-directive"
4242
import { ValidationErrors } from "./validation-errors"
43+
import { DeferredActionCallback } from "./deferred-action-callback"
4344

4445
export type KeyCaseValue = "dash" | "camel" | "snake"
4546

@@ -393,6 +394,8 @@ export class SpraypaintBase {
393394
temp_id?: string
394395
stale: boolean = false
395396
storeKey: string = ""
397+
onDeferredDestroy?: DeferredActionCallback
398+
onDeferredUpdate?: DeferredActionCallback
396399

397400
@nonenumerable afterSync?: (diff: Record<string, any>) => any | undefined
398401
@nonenumerable relationships: Record<
@@ -917,6 +920,13 @@ export class SpraypaintBase {
917920
throw err
918921
}
919922

923+
if (response.status === 202) {
924+
return await this._handleAcceptedResponse(
925+
response,
926+
this.onDeferredDestroy
927+
)
928+
}
929+
920930
let base = this.klass.baseClass as typeof SpraypaintBase
921931
base.store.destroy(this)
922932

@@ -964,6 +974,10 @@ export class SpraypaintBase {
964974
throw err
965975
}
966976

977+
if (response.status === 202) {
978+
return await this._handleAcceptedResponse(response, this.onDeferredUpdate)
979+
}
980+
967981
return await this._handleResponse(response, () => {
968982
this.fromJsonapi(
969983
response.jsonPayload.data,
@@ -974,6 +988,20 @@ export class SpraypaintBase {
974988
})
975989
}
976990

991+
private async _handleAcceptedResponse(
992+
response: any,
993+
callback: DeferredActionCallback | undefined
994+
): Promise<boolean> {
995+
if (response.jsonPayload && callback) {
996+
const responseObject = this.klass.fromJsonapi(
997+
response.jsonPayload.data,
998+
response.jsonPayload
999+
)
1000+
callback(responseObject)
1001+
}
1002+
return await this._handleResponse(response, () => {})
1003+
}
1004+
9771005
private async _handleResponse(
9781006
response: JsonapiResponse,
9791007
callback: () => void

src/request.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,14 @@ export class Request {
120120
) {
121121
let wasDelete =
122122
requestOptions.method === "DELETE" &&
123-
[202, 204, 200].indexOf(response.status) > -1
123+
[204, 200].indexOf(response.status) > -1
124124
if (wasDelete) return
125125

126126
let json
127127
try {
128128
json = await response.json()
129129
} catch (e) {
130+
if (response.status === 202) return
130131
throw new ResponseError(response, "invalid json", e)
131132
}
132133

test/fixtures.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ export class MultiWord extends ApplicationRecord {
119119
static jsonapiType = "multi_words"
120120
}
121121

122+
@Model()
123+
export class BackgroundJob extends ApplicationRecord {
124+
static jsonapiType = "background_jobs"
125+
}
126+
122127
export const TestJWTSubclass = ApplicationRecord.extend({})
123128

124129
export const NonJWTOwner = SpraypaintBase.extend({})

test/integration/persistence.test.ts

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, fetchMock } from "../test-helper"
2-
import { Person, PersonWithExtraAttr } from "../fixtures"
2+
import { Person, PersonWithExtraAttr, BackgroundJob } from "../fixtures"
33
import { JsonapiRequestDoc, JsonapiResponseDoc } from "../../src/index"
44

55
after(() => {
@@ -274,6 +274,54 @@ describe("Model persistence", () => {
274274
})
275275
})
276276
})
277+
278+
describe("when the server returns 202 accepted", () => {
279+
beforeEach(() => {
280+
fetchMock.restore()
281+
fetchMock.post("http://example.com/api/v1/people", { status: 202 })
282+
})
283+
284+
afterEach(() => {
285+
resetMocks()
286+
})
287+
288+
it("does not blow up", async () => {
289+
expect(instance.isPersisted).to.eq(false)
290+
const success = await instance.save()
291+
292+
expect(success).to.be.true
293+
expect(instance.isPersisted).to.eq(false)
294+
})
295+
})
296+
297+
describe("when the server returns 202 and a background job object", () => {
298+
beforeEach(() => {
299+
fetchMock.restore()
300+
301+
fetchMock.post("http://example.com/api/v1/people", {
302+
status: 202,
303+
body: { data: { id: "23", type: "background_jobs", attributes: {} } }
304+
})
305+
})
306+
307+
afterEach(() => {
308+
resetMocks()
309+
})
310+
311+
it("deserialize the response and return the job object", async () => {
312+
let job: BackgroundJob = new BackgroundJob()
313+
let populateJobOnResponse = (response: any) => {
314+
job = response
315+
}
316+
instance.onDeferredUpdate = populateJobOnResponse
317+
expect(instance.isPersisted).to.eq(false)
318+
await instance.save()
319+
320+
expect(instance.isPersisted).to.eq(false)
321+
expect(job.id).to.eq("23")
322+
expect(job.klass).to.eq(BackgroundJob)
323+
})
324+
})
277325
})
278326

279327
describe("#destroy", () => {
@@ -321,21 +369,48 @@ describe("Model persistence", () => {
321369
beforeEach(() => {
322370
fetchMock.restore()
323371

324-
fetchMock.mock({
325-
matcher: "http://example.com/api/v1/people/1",
326-
response: new Response({ status: 202 } as any)
327-
})
372+
fetchMock.delete("http://example.com/api/v1/people/1", { status: 202 })
328373
})
329374

330375
afterEach(() => {
331376
resetMocks()
332377
})
333378

334379
it("does not blow up", async () => {
380+
expect(instance.isPersisted).to.eq(true)
381+
const success = await instance.destroy()
382+
383+
expect(success).to.be.true
384+
expect(instance.isPersisted).to.eq(true)
385+
})
386+
})
387+
388+
describe("when the server returns 202 and a background job object", () => {
389+
beforeEach(() => {
390+
fetchMock.restore()
391+
392+
fetchMock.delete("http://example.com/api/v1/people/1", {
393+
status: 202,
394+
body: { data: { id: "23", type: "background_jobs", attributes: {} } }
395+
})
396+
})
397+
398+
afterEach(() => {
399+
resetMocks()
400+
})
401+
402+
it("deserialize the response and return the job object", async () => {
403+
let job: BackgroundJob = new BackgroundJob()
404+
let populateJobOnResponse = (response: any) => {
405+
job = response
406+
}
407+
instance.onDeferredDestroy = populateJobOnResponse
335408
expect(instance.isPersisted).to.eq(true)
336409
await instance.destroy()
337410

338-
expect(instance.isPersisted).to.eq(false)
411+
expect(instance.isPersisted).to.eq(true)
412+
expect(job.id).to.eq("23")
413+
expect(job.klass).to.eq(BackgroundJob)
339414
})
340415
})
341416

0 commit comments

Comments
 (0)