From 7f631c7126caeee08399e8ebdd0ffe7426d64f46 Mon Sep 17 00:00:00 2001 From: MaxHuster Date: Fri, 16 Oct 2020 16:03:46 +0200 Subject: [PATCH 1/3] add test --- .gitignore | 5 ++++- package-lock.json | 9 +++++++++ src/OBatch.ts | 17 ++++++++++++----- src/o.spec.ts | 23 +++++++++++++++++++++-- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 2224fac..74e208b 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,7 @@ node_modules npm-debug.log dist -.rpt2_cache \ No newline at end of file +.rpt2_cache + +# WebStorm & IntelliJ Platfrom +.idea/ diff --git a/package-lock.json b/package-lock.json index b1e1f6b..d789817 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1303,6 +1303,15 @@ "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", "dev": true }, + "agent-base": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", + "dev": true, + "requires": { + "debug": "4" + } + }, "ajv": { "version": "6.12.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", diff --git a/src/OBatch.ts b/src/OBatch.ts index cdda0e8..130f24b 100644 --- a/src/OBatch.ts +++ b/src/OBatch.ts @@ -8,6 +8,7 @@ export class OBatch { // "" here prevents 'undefined' at start of body under some conditions. private batchBody = ""; private batchUid; + private changesetUid; private batchConfig: OdataConfig; constructor( @@ -17,7 +18,8 @@ export class OBatch { private changeset: boolean = false, ) { this.batchConfig = { ...config, ...config.batch }; - this.batchUid = this.getUid(); + this.batchUid = this.generateUid(false); + this.changesetUid = this.generateUid(true); (this.batchConfig.headers as Headers).set( "Content-Type", `multipart/mixed;boundary=${this.batchUid}`, @@ -35,6 +37,7 @@ export class OBatch { let contentId = 0; this.batchBody += resources.map((req) => { contentId++; + return [ "", "Content-Type: application/http", @@ -43,13 +46,17 @@ export class OBatch { "", `${req.config.method} ${this.getRequestURL(req)} HTTP/1.1`, `${this.getHeaders(req)}`, - `${this.getBody(req)}` + `${this.getBody(req)}`, ].join(CRLF); }).join(`${CRLF}--${this.batchUid}`); this.batchBody += `${CRLF}--${this.batchUid}--${CRLF}`; } + public getBatchBody(): string { + return this.batchBody; + } + public async fetch(url: URL) { const req = new ORequest(url, { ...this.batchConfig, @@ -111,7 +118,7 @@ export class OBatch { "", `Content-Type: multipart/mixed;boundary=${this.batchUid}`, "", - `--${this.batchUid}` + `--${this.batchUid}`, ].join(CRLF); } else if (changeRes.length > 0) { this.batchBody = `--${this.batchUid}`; @@ -143,7 +150,7 @@ export class OBatch { return ""; } - private getUid() { + private generateUid(changeset = false) { let d = new Date().getTime(); const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { const r = (d + Math.random() * 16) % 16 | 0; @@ -151,7 +158,7 @@ export class OBatch { return (c === "x" ? r : (r & 0x7) | 0x8).toString(16); }); return `${ - this.changeset + changeset ? this.batchConfig.batch.changsetBoundaryPrefix : this.batchConfig.batch.boundaryPrefix }${uuid}`; diff --git a/src/o.spec.ts b/src/o.spec.ts index f5a8579..2c854f6 100644 --- a/src/o.spec.ts +++ b/src/o.spec.ts @@ -1,4 +1,4 @@ -import { o } from "./o"; +import {o, OBatch} from "./o"; describe("initialize a new oHandler", () => { test("url with string", () => { @@ -358,7 +358,7 @@ describe("Create, Update and Delete request", () => { beforeAll(async () => { // Use the non restier service as it has CORS enabled const response: Response = await o( - "https://services.odata.org/V4/TripPinServiceRW/", + "http://services.odata.org/V4/TripPinServiceRW/", ) .get() .fetch() as Response; @@ -499,6 +499,25 @@ describe("Batching", () => { expect(data[2].Name).toBe("New"); }); + test("Batch multiple GET requests and patch something with useChangeset", async () => { + oHandler.config.batch.useChangset = true; + + // given + const [resource1, resource2] = ["People", "Airlines('AA')"]; + // when + const request = oHandler + .get(resource1) + .patch(resource2, { Name: "New" }) + .get(resource2); + const batch = new OBatch(request.requests, request.config, null); + window.console.log(batch.getBatchBody()); + const data = await request.batch(); + // expect + expect(data.length).toBe(3); + expect(data[1]).toBe(204); + expect(data[2].Name).toBe("New"); + }); + // Content ID seems to have a problem in the test implementation (or I don't get the right implementation) // tested with postman and I always get Resource not found for the segment '$1' xtest("add something and directly patch it with Content-Id", async () => { From 2c83a0a228d1e58103bc07a632f4110031fbfd4e Mon Sep 17 00:00:00 2001 From: MaxHuster Date: Mon, 19 Oct 2020 13:09:24 +0200 Subject: [PATCH 2/3] basic transaction working --- src/OBatch.ts | 65 +++++++++++++++++++++++++-------------------------- src/o.spec.ts | 18 ++++++++------ 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/src/OBatch.ts b/src/OBatch.ts index 130f24b..b672950 100644 --- a/src/OBatch.ts +++ b/src/OBatch.ts @@ -8,7 +8,6 @@ export class OBatch { // "" here prevents 'undefined' at start of body under some conditions. private batchBody = ""; private batchUid; - private changesetUid; private batchConfig: OdataConfig; constructor( @@ -18,11 +17,10 @@ export class OBatch { private changeset: boolean = false, ) { this.batchConfig = { ...config, ...config.batch }; - this.batchUid = this.generateUid(false); - this.changesetUid = this.generateUid(true); + this.batchUid = this.getUid(); (this.batchConfig.headers as Headers).set( - "Content-Type", - `multipart/mixed;boundary=${this.batchUid}`, + "Content-Type", + `multipart/mixed; boundary=${this.batchUid}`, ); if (this.batchConfig.batch.useChangset) { @@ -37,7 +35,6 @@ export class OBatch { let contentId = 0; this.batchBody += resources.map((req) => { contentId++; - return [ "", "Content-Type: application/http", @@ -46,15 +43,17 @@ export class OBatch { "", `${req.config.method} ${this.getRequestURL(req)} HTTP/1.1`, `${this.getHeaders(req)}`, - `${this.getBody(req)}`, + `${this.getBody(req)}` ].join(CRLF); }).join(`${CRLF}--${this.batchUid}`); this.batchBody += `${CRLF}--${this.batchUid}--${CRLF}`; - } - - public getBatchBody(): string { - return this.batchBody; + if(!changeset){ + (this.batchConfig.headers as Headers).set( + "Content-Type", + `multipart/mixed;boundary=${this.batchUid}`, + ); + } } public async fetch(url: URL) { @@ -118,7 +117,7 @@ export class OBatch { "", `Content-Type: multipart/mixed;boundary=${this.batchUid}`, "", - `--${this.batchUid}`, + `--${this.batchUid}` ].join(CRLF); } else if (changeRes.length > 0) { this.batchBody = `--${this.batchUid}`; @@ -150,7 +149,7 @@ export class OBatch { return ""; } - private generateUid(changeset = false) { + private getUid() { let d = new Date().getTime(); const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { const r = (d + Math.random() * 16) % 16 | 0; @@ -158,34 +157,34 @@ export class OBatch { return (c === "x" ? r : (r & 0x7) | 0x8).toString(16); }); return `${ - changeset + this.changeset ? this.batchConfig.batch.changsetBoundaryPrefix : this.batchConfig.batch.boundaryPrefix }${uuid}`; } private getHeaders(req: ORequest): string { - // Request headers can be Headers | string[][] | Record. - // A new Headers instance around them allows treatment of all three types - // to be the same. This also applies security last two could bypass. - const headers = new Headers(req.config.headers || undefined) as any; - // Convert each header to single string. - // Headers is iterable. Array.from is needed instead of Object.keys. - const mapped = Array.from(headers).map(([k, v]) => `${k}: ${v}`); - if (mapped.length) { - // Need to ensure a blank line between HEADERS and BODY. When there are - // headers, it must be added here. Otherwise blank is added in ctor. - mapped.push(""); - } - return mapped.join(CRLF); + // Request headers can be Headers | string[][] | Record. + // A new Headers instance around them allows treatment of all three types + // to be the same. This also applies security last two could bypass. + const headers = new Headers(req.config.headers || undefined) as any; + // Convert each header to single string. + // Headers is iterable. Array.from is needed instead of Object.keys. + const mapped = Array.from(headers).map(([k, v]) => `${k}: ${v}`); + if (mapped.length) { + // Need to ensure a blank line between HEADERS and BODY. When there are + // headers, it must be added here. Otherwise blank is added in ctor. + mapped.push(""); + } + return mapped.join(CRLF); } private getRequestURL(req: ORequest): string { - let href = req.url.href; - if (this.batchConfig.batch.useRelativeURLs) { - // Strip away matching root from request. - href = href.replace(this.batchConfig.rootUrl.href, ''); - } - return href; + let href = req.url.href; + if (this.batchConfig.batch.useRelativeURLs) { + // Strip away matching root from request. + href = href.replace(this.batchConfig.rootUrl.href, ''); + } + return href; } } diff --git a/src/o.spec.ts b/src/o.spec.ts index 2c854f6..b4a19c2 100644 --- a/src/o.spec.ts +++ b/src/o.spec.ts @@ -499,21 +499,25 @@ describe("Batching", () => { expect(data[2].Name).toBe("New"); }); - test("Batch multiple GET requests and patch something with useChangeset", async () => { + test("Batch POST and PATCH with useChangeset=true", async () => { oHandler.config.batch.useChangset = true; - // given + // given const [resource1, resource2] = ["People", "Airlines('AA')"]; + const resouce1data = { + FirstName: "Bar", + LastName: "Foo", + UserName: "foobar" + Math.random(), + }; // when const request = oHandler - .get(resource1) - .patch(resource2, { Name: "New" }) - .get(resource2); + .post(resource1, resouce1data) + .patch(resource2, { Name: "New" }); const batch = new OBatch(request.requests, request.config, null); - window.console.log(batch.getBatchBody()); const data = await request.batch(); + console.log(JSON.stringify(data, null, 2)); // expect - expect(data.length).toBe(3); + expect(data.length).toBe(2); // we only expect a result of 2 because the changeset returns only not GET requests expect(data[1]).toBe(204); expect(data[2].Name).toBe("New"); }); From d2b2c936eb6673c352bf598e828c03c0fd27847b Mon Sep 17 00:00:00 2001 From: MaxHuster Date: Mon, 19 Oct 2020 13:34:46 +0200 Subject: [PATCH 3/3] add parsing of changeset message --- src/OBatch.ts | 15 ++++++++++++++- src/o.spec.ts | 7 +++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/OBatch.ts b/src/OBatch.ts index b672950..a5a9346 100644 --- a/src/OBatch.ts +++ b/src/OBatch.ts @@ -83,11 +83,21 @@ export class OBatch { const splitData = responseData.split(`--${boundary}`); splitData.shift(); splitData.pop(); + let wasWithChangesetresponse = false; const parsedData = splitData.map((data) => { const dataSegments = data.trim().split("\r\n\r\n"); - if (dataSegments.length === 0 || dataSegments.length > 3) { + if (dataSegments.length === 0) { // we are unable to parse -> return all return data; + } else if (dataSegments.length > 3) { + const header = dataSegments.find( + (x) => x.startsWith("Content-Type: ") && x.includes("boundary=changesetresponse_")); + if (!header) { + return data; + } + dataSegments.shift(); + wasWithChangesetresponse = true; + return this.parseResponse(dataSegments.join("\r\n\r\n"), header); } else if (dataSegments.length === 3) { // if length >= 3 we have a body, try to parse if JSON and return that! try { @@ -102,6 +112,9 @@ export class OBatch { return +dataSegments[1].split(" ")[1]; } }); + if (wasWithChangesetresponse) { + return parsedData[0]; + } return parsedData; } diff --git a/src/o.spec.ts b/src/o.spec.ts index b4a19c2..6348772 100644 --- a/src/o.spec.ts +++ b/src/o.spec.ts @@ -506,7 +506,7 @@ describe("Batching", () => { const [resource1, resource2] = ["People", "Airlines('AA')"]; const resouce1data = { FirstName: "Bar", - LastName: "Foo", + LastName: "Foo is cool", UserName: "foobar" + Math.random(), }; // when @@ -515,11 +515,10 @@ describe("Batching", () => { .patch(resource2, { Name: "New" }); const batch = new OBatch(request.requests, request.config, null); const data = await request.batch(); - console.log(JSON.stringify(data, null, 2)); // expect - expect(data.length).toBe(2); // we only expect a result of 2 because the changeset returns only not GET requests + expect(data.length).toBe(2); + expect(data[0].LastName).toBe(resouce1data.LastName); expect(data[1]).toBe(204); - expect(data[2].Name).toBe("New"); }); // Content ID seems to have a problem in the test implementation (or I don't get the right implementation)