UNPKG

odata

Version:

o.js is a isomorphic Odata Javascript library to simplify the request of data. The main goal is to build a standalone, lightweight and easy to understand Odata lib.

653 lines (560 loc) 17.4 kB
import { o, OBatch } from "./o"; import { OdataConfig } from "./OdataConfig"; import buildQuery from "odata-query"; describe("initialize a new oHandler", () => { test("url with string", () => { // given const url = "http://odata.org/"; // when const when = o(url); // expect expect((when.config.rootUrl as URL).href).toEqual(url); }); test("url with string and root config", () => { // given const url = "http://odata.org/"; const config = { rootUrl: "http://a/" }; // when const when = o(url, config); // expect expect((when.config.rootUrl as URL).href).toEqual(url); }); test("only with rootUrl config", () => { // given const url = ""; const config = { rootUrl: "http://a/" }; // when const when = o(url, config); // expect expect((when.config.rootUrl as URL).href).toEqual(config.rootUrl); }); test("only with rootUrl and a given url", () => { // given const url = "foo"; const config = { rootUrl: "http://bar/" }; // when const when = o(url, config); // expect expect((when.config.rootUrl as URL).href).toEqual(`${config.rootUrl}foo`); }); test("no rootUrl given and url given not a valid url (should switch to window.location.href)", () => { // given const url = "foo"; // when const when = o(url); // expect expect((when.config.rootUrl as URL).href).toEqual(`http://localhost/foo`); }); test("config should be default if not given", () => { // given const url = "foo"; // when const when = o(url); // expect expect(when.config).toBeDefined(); expect(when.config).toMatchSnapshot(); }); test("config should be extended if something given.", () => { // given const url = "foo"; const config: Partial<OdataConfig> = { mode: "no-cors", rootUrl: "http://bar.de/foo", }; // when const when = o(url, config); // expect expect(when.config).toBeDefined(); expect(when.config.mode).toBe("no-cors"); expect(when.config.referrer).toBe("client"); expect((when.config.rootUrl as URL).href).toBe("http://bar.de/foo/foo"); }); }); describe("Instant request", () => { test("Request any thing that is put in the init request", async () => { // when const data = await o( "https://services.odata.org/V4/TripPinServiceRW/People?$top=2" ) .get() .query(); // expect expect(data.length).toBe(2); }); test("Still allow chaining and multiple requests", async () => { // given const [resource1, resource2] = ["People", "Airlines"]; // when const data = await o("https://services.odata.org/V4/TripPinServiceRW/") .get(resource1) .get(resource2) .query({ $top: 2 }); // expect expect(data.length).toBe(2); expect(data[0].length).toBe(2); expect(data[1].length).toBe(2); }); test("Attach the correct queries to the request", async () => { // when const data = await o( "https://services.odata.org/V4/TripPinServiceRW/People?$top=2", { query: { $top: 1, $filter: `FirstName eq 'john'` }, } ) .get() .fetch(); // expect expect(decodeURIComponent((data as Response).url)).toContain( "People?$top=1&$filter=FirstName eq 'john'" ); }); test("Attach the correct queries to the request if a string is used", async () => { // when const data = await o( "https://services.odata.org/V4/TripPinServiceRW/People?$top=2" ) .get() .fetch("?$top=1&$filter=FirstName eq 'john'"); // expect expect(decodeURIComponent((data as Response).url)).toContain( "People?$top=1&$filter=FirstName eq 'john'" ); }); test("Attach the correct queries to the request if a odata-query is used", async () => { // given const filter = { not: { and: [{ FirstName: 'John' }, { LastName: 'Foo' }], }, }; // when const data = await o( "https://services.odata.org/V4/TripPinServiceRW/People?$top=2" ) .get() .fetch(buildQuery({ filter })); // expect expect(decodeURIComponent((data as Response).url)).toContain( "$filter=not(((FirstName eq 'John') and (LastName eq 'Foo')))&$top=2" ); }); test("Use buildQuery in get", async () => { // given const key = '1' const filter = { not: { and: [{ FirstName: 'John' }, { LastName: 'Foo' }], }, }; // when const data = await o( "https://services.odata.org/V4/TripPinServiceRW/" ) .get('People' + buildQuery({ key, filter, top: 3 })) .fetch(); // expect expect(decodeURIComponent((data as Response).url)).toContain( "/People('1')?$filter=not(((FirstName eq 'John') and (LastName eq 'Foo')))&$top=3" ); }); test("Check right URL Params override. query-parameter in fetch()/query() wins over query-config", async () => { // when const data = await o( "https://services.odata.org/V4/TripPinServiceRW/People", { query: { $top: 1 }, } ) .get() .fetch({ $top: 2 }); // expect expect(decodeURIComponent((data as Response).url)).toContain( "People?$top=2" ); }); test("Check right URL Params override. query-parameter in fetch()/query() wins over baseUrl", async () => { // when const data = await o( "https://services.odata.org/V4/TripPinServiceRW/People?$top=1" ) .get() .fetch({ $top: 2 }); // expect expect(decodeURIComponent((data as Response).url)).toContain( "People?$top=2" ); }); test("Check right URL Params override. query-config wins over baseUrl", async () => { // when const data = await o( "https://services.odata.org/V4/TripPinServiceRW/People?$top=1", { query: { $top: 2 }, } ) .get() .fetch(); // expect expect(decodeURIComponent((data as Response).url)).toContain( "People?$top=2" ); }); }); describe("Request handling", () => { let oHandler; beforeEach(() => { oHandler = o("https://services.odata.org/V4/TripPinServiceRW/"); }); test("Queue requests", () => { // given const resource = "People"; // when oHandler.get(resource); // expect expect(oHandler.pending).toBe(1); // when oHandler.get(resource).get(resource); // expect expect(oHandler.pending).toBe(3); }); test("Clean queued request after query", async () => { // given const resource = "People"; // when await oHandler.get(resource).get(resource).query(); // expect expect(oHandler.pending).toBe(0); }); test("Clean queued request after batch", async () => { // given const resource = "People"; // when try { await oHandler.get(resource).get(resource).query(); } catch (ex) { console.log(ex); // intended empty } // expect expect(oHandler.pending).toBe(0); }); test("Attach the correct queries to the request", async () => { // given const resource = "People"; // when const requests = oHandler .get(resource) .get(resource) .fetch({ $top: 1, $filter: `FirstName eq 'john'` }); const req = await requests; // expect expect(decodeURIComponent(req[0].url)).toContain( "People?$top=1&$filter=FirstName eq 'john'" ); }); test("On request to one resource only, don't return an array", async () => { // given const resource = "People"; // when const requests = oHandler.get(resource).fetch(); const req = await requests; // expect expect(Array.isArray(req)).toBe(false); }); }); describe("GET request", () => { let oHandler; beforeAll(() => { oHandler = o("https://services.odata.org/V4/TripPinServiceRW/"); }); test("Request to a resource should return an array", async () => { // given const resource = "People"; // when const data = await oHandler.get(resource).query({ $top: 4, }); // expect expect(Array.isArray(data)).toBe(true); expect(data.length).toBe(4); }); test("Request to one entity should return a object", async () => { // given const resource = "People('russellwhyte')"; // when const data = await oHandler.get(resource).query(); // expect expect(Array.isArray(data)).toBe(false); expect(typeof data).toBe("object"); }); test("Request to $top=1 should return a array", async () => { // given const resource = "People"; // when const data = await oHandler.get(resource).query({ $top: 1 }); // expect expect(Array.isArray(data)).toBe(true); expect(data.length).toBe(1); }); test("Request multiple resources or entities", async () => { // given const resource1 = "People('russellwhyte')"; const resource2 = "People"; // when const data = await oHandler.get(resource1).get(resource2).query(); // expect expect(Array.isArray(data)).toBe(true); expect(data.length).toBe(2); expect(Array.isArray(data[0])).toBe(false); expect(Array.isArray(data)).toBe(true); }); test("Request a entity that cannot be found", async () => { // given const resource = "People('foo')"; // when try { await oHandler.get(resource).query(); } catch (res) { // expect expect(res.status).toBe(404); } }); test("If one request fails in a query sequent, throw all", async () => { // given const resource1 = "People('russellwhyte')"; const resource2 = "People('unknown')"; // when try { await oHandler.get(resource1).get(resource2).query(); } catch (res) { // expect expect(res.status).toBe(404); } }); }); describe("Error handling", () => { let oHandler; let onError; beforeEach(() => { onError = jest.fn(); oHandler = o("https://services.odata.org/V4/TripPinServiceRW/", { onError }); }); afterEach(() => { jest.restoreAllMocks(); }); test.each([ "query", "fetch", "batch", ])("Callback onError is called in %s when network call fails", async (method) => { // given const resource = "People"; const fetchError = new TypeError("Failed to fetch"); jest .spyOn(window, "fetch") .mockRejectedValue(fetchError); // when / expect await expect(async () => await oHandler.get(resource)[method]()).rejects.toBe(fetchError); expect(onError).toHaveBeenCalledTimes(1); expect(onError).toHaveBeenCalledWith(oHandler, fetchError); }); // Unlike fetch and batch methods, query is the only one that analyze the response status code and throws an error // if the status code is not 2xx. test("Callback onError is called in query when response code is 404", async () => { // given const resource = "UnknownResource"; // when / expect await expect(async () => await oHandler.get(resource).query()).rejects.toMatchObject({ status: 404 }); expect(onError).toHaveBeenCalledTimes(1); expect(onError).toHaveBeenCalledWith(oHandler, expect.objectContaining({ status: 404 })); }); }); describe("Create, Update and Delete request", () => { let oHandler; beforeAll(async () => { oHandler = o( "https://services.odata.org/V4/TripPinServiceRW/(S(ojstest))/", { headers: { "If-match": "*", "Content-Type": "application/json" }, } ); }); test("POST a person and request it afterwards", async () => { // given const resource = "People"; const data = { FirstName: "Foo", LastName: "Bar", UserName: "barfoo" + Math.random(), }; // when const response = await oHandler .post(resource, data) .get(`${resource}('${data.UserName}')`) .query(); // expect expect(Array.isArray(response)).toBe(true); expect(Array.isArray(response[0])).toBe(false); expect(Array.isArray(response[1])).toBe(false); expect(response[0].FirstName).toBe(data.FirstName); expect(response[1].LastName).toBe(data.LastName); expect(response[1].UserName).toBe(data.UserName); }); test("POST a person and DELETE it afterwards", async () => { // given const resource = "People"; const data = { FirstName: "Bar", LastName: "Foo", UserName: "foobar" + Math.random(), }; // when const response = await oHandler .post(resource, data) .delete(`${resource}('${data.UserName}')`) .query(); // expect expect(Array.isArray(response)).toBe(true); expect(Array.isArray(response[0])).toBe(false); expect(Array.isArray(response[1])).toBe(false); expect(response[0].FirstName).toBe(data.FirstName); expect(response[0].LastName).toBe(data.LastName); expect(response[0].UserName).toBe(data.UserName); expect(response[1].status).toBe(204); }); test("POST a person and PATCH it afterwards", async () => { // given const resource = "People"; const data = { FirstName: "Bar", LastName: "Foo", UserName: "foobar" + Math.random(), }; // when const response = await oHandler .post(resource, data) .patch(`${resource}('${data.UserName}')`, { FirstName: data.LastName }) .get(`${resource}('${data.UserName}')`) .query(); // expect expect(Array.isArray(response)).toBe(true); expect(Array.isArray(response[0])).toBe(false); expect(Array.isArray(response[1])).toBe(false); expect(response[0].FirstName).toBe(data.FirstName); expect(response[0].LastName).toBe(data.LastName); expect(response[1].status).toBe(204); expect(response[2].FirstName).toBe(data.LastName); }); test("POST a person with FormData", async () => { // given const resource = "People"; const data = new FormData(); data.append("FirstName", "Bar"); // when try { const response = await oHandler.post(resource, data).query(); } catch (ex) { // expect: FormData is not supported, so this error code is correct expect(ex.status).toBe(415); } }); }); describe("Batching", () => { let oHandler; beforeAll(async () => { // Use the non restier service as it has CORS enabled const response: Response = (await o( "https://services.odata.org/V4/TripPinServiceRW/" ) .get() .fetch()) as Response; oHandler = o(response.url, { headers: { "If-match": "*", "Content-Type": "application/json" }, }); }); test("Batch multiple GET requests", async () => { // given const [resource1, resource2] = ["People", "Airlines"]; // when const data = await oHandler.get(resource1).get(resource2).batch(); // expect expect(data.length).toBe(2); expect(data[0].body.length).toBeDefined(); }); test("Batch multiple GET requests and allow to add a query", async () => { // given const [resource1, resource2] = ["People", "Airlines"]; // when const data = await oHandler .get(resource1) .get(resource2) .batch({ $top: 2 }); // expect expect(data[0].body.length).toBe(2); }); test("Batch multiple GET requests and patch something", async () => { // given const [resource1, resource2] = ["People", "Airlines('AA')"]; // when const data = await oHandler .get(resource1) .patch(resource2, { Name: "New" }) .get(resource2) .batch(); // expect expect(data.length).toBe(3); expect(data[1].status).toBe(204); expect(data[2].body.Name).toBe("New"); }); test("Batch POST and PATCH with useChangeset=true", async () => { oHandler.config.batch.useChangset = true; // given const [resource1, resource2] = ["People", "Airlines('AA')"]; const resouce1data = { FirstName: "Bar", LastName: "Foo is cool", UserName: "foobar" + Math.random(), }; // when const request = oHandler .post(resource1, resouce1data) .patch(resource2, { Name: "New" }); const batch = new OBatch(request.requests, request.config, null); const data = await request.batch(); // expect expect(data.length).toBe(2); expect(data[0].body.LastName).toBe(resouce1data.LastName); expect(data[1].status).toBe(204); }); test("Clean queued request after batch", async () => { // given const [resource1, resource2] = ["People", "Airlines"]; // when await oHandler.get(resource1).get(resource2).batch(); // expect expect(oHandler.pending).toBe(0); }); // 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 () => { // given oHandler.config.batch.useChangeset = true; const resource = "People"; const data = { FirstName: "Bar", LastName: "Foo", UserName: "foobar" + Math.random(), }; // when const result = await oHandler .post(resource, data) .patch("$1", { LastName: "Bar" }) .get(`${resource}('${data.UserName}')`) .batch(); // expect expect(result.length).toBe(3); expect(result[1].status).toBe(204); expect(result[2].body.LastName).toBe("Bar"); }); });