UNPKG

terriajs

Version:

Geospatial data visualization platform.

512 lines (453 loc) 17.2 kB
import { http, HttpResponse } from "msw"; import { configure, reaction, runInAction } from "mobx"; import Cartographic from "terriajs-cesium/Source/Core/Cartographic"; import GeoJsonDataSource from "terriajs-cesium/Source/DataSources/GeoJsonDataSource"; import isDefined from "../../../../lib/Core/isDefined"; import Result from "../../../../lib/Core/Result"; import TerriaError from "../../../../lib/Core/TerriaError"; import MappableMixin from "../../../../lib/ModelMixins/MappableMixin"; import CsvCatalogItem from "../../../../lib/Models/Catalog/CatalogItems/CsvCatalogItem"; import WebProcessingServiceCatalogFunction from "../../../../lib/Models/Catalog/Ows/WebProcessingServiceCatalogFunction"; import WebProcessingServiceCatalogFunctionJob from "../../../../lib/Models/Catalog/Ows/WebProcessingServiceCatalogFunctionJob"; import CommonStrata from "../../../../lib/Models/Definition/CommonStrata"; import DateTimeParameter from "../../../../lib/Models/FunctionParameters/DateTimeParameter"; import EnumerationParameter from "../../../../lib/Models/FunctionParameters/EnumerationParameter"; import GeoJsonParameter from "../../../../lib/Models/FunctionParameters/GeoJsonParameter"; import LineParameter from "../../../../lib/Models/FunctionParameters/LineParameter"; import PointParameter from "../../../../lib/Models/FunctionParameters/PointParameter"; import PolygonParameter from "../../../../lib/Models/FunctionParameters/PolygonParameter"; import RectangleParameter from "../../../../lib/Models/FunctionParameters/RectangleParameter"; import StringParameter from "../../../../lib/Models/FunctionParameters/StringParameter"; import Terria from "../../../../lib/Models/Terria"; import { worker } from "../../../mocks/browser"; import "../../../SpecHelpers"; import regionMapping from "../../../../wwwroot/data/regionMapping.json"; configure({ enforceActions: "observed", computedRequiresReaction: true }); import processDescriptionsXml from "../../../../wwwroot/test/WPS/ProcessDescriptions.xml"; import executeResponseXml from "../../../../wwwroot/test/WPS/ExecuteResponse.xml"; import failedExecuteResponseXml from "../../../../wwwroot/test/WPS/FailedExecuteResponse.xml"; import pendingExecuteResponseXml from "../../../../wwwroot/test/WPS/PendingExecuteResponse.xml"; describe("WebProcessingServiceCatalogFunction", function () { let wps: WebProcessingServiceCatalogFunction; beforeEach(function () { const terria = initTerria(); wps = new WebProcessingServiceCatalogFunction("test", terria); runInAction(() => { wps.setTrait("definition", "url", "http://example.com/wps"); wps.setTrait("definition", "identifier", "someId"); }); worker.use( http.get("http://example.com/wps", ({ request }) => { const url = new URL(request.url); const wpsRequest = url.searchParams.get("request"); if (wpsRequest === "DescribeProcess") { return new HttpResponse(processDescriptionsXml, { headers: { "Content-Type": "text/xml" } }); } if (wpsRequest === "Execute") { return new HttpResponse(executeResponseXml, { headers: { "Content-Type": "text/xml" } }); } throw new Error(`Unexpected WPS GET request: ${url.search}`); }), http.post("http://example.com/wps", ({ request }) => { const url = new URL(request.url); if (url.searchParams.get("request") !== "Execute") throw new Error(`Unexpected WPS POST request: ${url.search}`); return new HttpResponse(executeResponseXml, { headers: { "Content-Type": "text/xml" } }); }), http.get("*/build/TerriaJS/data/regionMapping.json", () => HttpResponse.json(regionMapping) ) ); }); it("has a type & typeName", function () { expect(WebProcessingServiceCatalogFunction.type).toBe("wps"); expect(wps.typeName).toBe("Web Processing Service (WPS)"); }); describe("when loading", function () { it("should correctly query the DescribeProcess endpoint", async function () { await wps.loadMetadata(); expect(wps.functionParameters.length).toBe(2); expect(wps.functionParameters.map(({ type }) => type)).toEqual([ "string", "geojson" ]); }); }); describe("when invoked", function () { let dispose: () => void; let disposeMapItems: () => void; let job: WebProcessingServiceCatalogFunctionJob; beforeEach(async function () { dispose = reaction( () => wps.parameters, () => {} ); await wps.loadMetadata(); runInAction(() => { const param = wps.functionParameters.find( (p) => p.type === "geojson" ) as GeoJsonParameter; param.subtype = GeoJsonParameter.PointType; param.setValue(CommonStrata.user, { longitude: 2.5302435855103993, latitude: -0.6592349301568685, height: -1196.8235676901866 }); }); job = (await wps.submitJob()) as WebProcessingServiceCatalogFunctionJob; disposeMapItems = reaction( () => job.mapItems, () => {} ); }); afterEach(function () { dispose(); disposeMapItems(); }); it("makes a POST request to the Execute endpoint", function () { expect(job.identifier).toBe("someId"); // expect(job.).toMatch(/geometry=/); expect(job.jobStatus).toBe("finished"); }); it("makes a GET request to the Execute endpoint when `executeWithHttpGet` is true", function () { runInAction(() => wps.setTrait("definition", "executeWithHttpGet", true)); expect(job.identifier).toBe("someId"); // expect(job.).toMatch(/geometry=/); expect(job.wpsResponse).toBeDefined(); expect(job.jobStatus).toBe("finished"); }); it("adds a ResultPendingCatalogItem to the workbench", function () { expect(job.inWorkbench).toBeTruthy(); }); }); describe("on success", function () { let job: WebProcessingServiceCatalogFunctionJob; beforeEach(async function () { let dispose: any; job = (await wps.submitJob()) as WebProcessingServiceCatalogFunctionJob; await new Promise<void>((resolve) => { dispose = reaction( () => job.downloadedResults, () => { if (job.downloadedResults) resolve(); }, { fireImmediately: true } ); }); dispose(); }); it("adds a WebProcessingServiceCatalogFunctionJob to workbench", function () { expect(job.inWorkbench).toBeTruthy(); }); it("adds result to workbench", function () { expect(job.results.length).toBe(2); expect(MappableMixin.isMixedInto(job.results[0])).toBeTruthy(); expect(MappableMixin.isMixedInto(job.results[1])).toBeTruthy(); expect(job.results[0].inWorkbench).toBeTruthy(); expect(job.results[1].inWorkbench).toBeTruthy(); }); it("adds a new catalog member for the output", function () { expect(job.results[0].type).toBe(CsvCatalogItem.type); }); it("adds a short report", function () { expect(job.shortReportSections[0].content).toBe( "Chart Vegetation Cover generated." ); }); it("returns mapItems", function () { expect(job.mapItems.length).toBe(1); expect(job.mapItems[0]).toEqual(jasmine.any(GeoJsonDataSource)); }); it("defines a rectangle", function () { expect(job.rectangle).toBeDefined(); }); }); describe("otherwise if `statusLocation` is set", function () { it("polls the statusLocation for the result", async function () { worker.use( http.post( "http://example.com/wps", () => new HttpResponse(pendingExecuteResponseXml, { headers: { "Content-Type": "text/xml" } }) ), http.get( "http://example.com/ows", () => new HttpResponse(executeResponseXml, { headers: { "Content-Type": "text/xml" } }) ) ); const job = await wps.submitJob(); const dispose1 = reaction( () => job.mapItems, () => {} ); expect(job.jobStatus).toBe("running"); let dispose2: any; // Wait for job to finish polling, then check if finished await new Promise<void>((resolve) => { dispose2 = reaction( () => job.refreshEnabled, () => { if (!job.refreshEnabled) { expect(job.jobStatus).toBe("finished"); return job.downloadResults().then(() => resolve()); } }, { fireImmediately: true } ); }); dispose1(); dispose2(); }); it("stops polling if pendingItem is removed from the workbench", async function () { spyOn(wps.terria.workbench, "add").and.callFake(() => Promise.resolve(Result.none()) ); // do nothing worker.use( http.post( "http://example.com/wps", () => new HttpResponse(pendingExecuteResponseXml, { headers: { "Content-Type": "text/xml" } }) ) ); // Note: we don't stubRequest "http://example.com/ows?check_status/123" here - so an error will be thrown if the job polls for a result const job = await wps.submitJob(); expect(job.jobStatus).toBe("running"); }); }); describe("on failure", function () { let dispose: () => void; beforeEach(async function () { dispose = reaction( () => wps.parameters, () => {} ); await wps.loadMetadata(); runInAction(() => { const param = wps.functionParameters.find( (p) => p.type === "geojson" ) as GeoJsonParameter; param.subtype = GeoJsonParameter.PointType; param.setValue(CommonStrata.user, { longitude: 2.5302435855103993, latitude: -0.6592349301568685, height: -1196.8235676901866 }); }); }); afterEach(function () { dispose(); }); it("marks the ResultPendingCatalogItem as failed - for polling results", async function () { worker.use( http.post( "http://example.com/wps", () => new HttpResponse(pendingExecuteResponseXml, { headers: { "Content-Type": "text/xml" } }) ), http.get( "http://example.com/ows", () => new HttpResponse(failedExecuteResponseXml, { headers: { "Content-Type": "text/xml" } }) ) ); const job = (await wps.submitJob()) as WebProcessingServiceCatalogFunctionJob; const dispose1 = reaction( () => job.mapItems, () => {} ); let dispose2: any; // Wait for job to finish polling, then check if failed await new Promise<void>((resolve) => { dispose2 = reaction( () => job.refreshEnabled, () => { if (!job.refreshEnabled) { expect(job.jobStatus).toBe("error"); expect(job.shortReport).toBeDefined(); expect(job.shortReport).toMatch(/invocation failed/i); resolve(); } }, { fireImmediately: true } ); }); dispose1(); dispose2(); }); it("marks the ResultPendingCatalogItem as failed", async function () { worker.use( http.post( "http://example.com/wps", () => new HttpResponse(failedExecuteResponseXml, { headers: { "Content-Type": "text/xml" } }) ) ); try { const job = await wps.submitJob(); expect(job).toBeUndefined(); } catch (error: any) { expect(error).toBeDefined(); expect(error instanceof TerriaError).toBeTruthy(); expect(error.message).toBe( "One of the identifiers passed does not match with any of the processes offered by this server" ); } }); }); describe("convertInputToParameter", function () { it("works for a simple input", function () { const parameter = wps.convertInputToParameter(wps, { Identifier: "geometry_id", Title: "Geometry ID", Abstract: "ID of the input", LiteralData: { AnyValue: {} }, minOccurs: 1 }); expect(parameter).toBeDefined(); if (isDefined(parameter)) { expect(parameter.id).toBe("geometry_id"); expect(parameter.name).toBe("Geometry ID"); expect(parameter.description).toBe("ID of the input"); expect(parameter.isRequired).toBe(true); } }); it("converts LiteralData input with `AllowedValues` to EnumerationParameter", function () { const parameter = wps.convertInputToParameter(wps, { Identifier: "geometry_id", LiteralData: { AllowedValues: { Value: ["Point", "Polygon"] } } }); expect(parameter).toEqual(jasmine.any(EnumerationParameter)); if (parameter) { const enumParameter = parameter as EnumerationParameter; expect(enumParameter.options).toEqual([ { id: "Point" }, { id: "Polygon" } ]); } }); it("converts LiteralData input with `AllowedValue` to EnumerationParameter", function () { const parameter = wps.convertInputToParameter(wps, { Identifier: "geometry_id", LiteralData: { AllowedValue: { Value: "Point" } } }); expect(parameter).toEqual(jasmine.any(EnumerationParameter)); if (parameter) { const enumParameter = parameter as EnumerationParameter; expect(enumParameter.options).toEqual([{ id: "Point" }]); } }); it("converts LiteralData input with `AnyValue` to StringParameter", function () { const parameter = wps.convertInputToParameter(wps, { Identifier: "geometry_id", LiteralData: { AnyValue: {} } }); expect(parameter).toEqual(jasmine.any(StringParameter)); }); it("converts ComplexData input with datetime schema to DateTimeParameter", function () { const parameter = wps.convertInputToParameter(wps, { Identifier: "geometry", ComplexData: { Default: { Format: { Schema: "http://www.w3.org/TR/xmlschema-2/#dateTime" } } } }); expect(parameter).toEqual(jasmine.any(DateTimeParameter)); }); it("converts ComplexData input with point schema to PointParameter", function () { const parameter = wps.convertInputToParameter(wps, { Identifier: "geometry", ComplexData: { Default: { Format: { Schema: "http://geojson.org/geojson-spec.html#point" } } } }); expect(parameter).toEqual(jasmine.any(PointParameter)); }); it("converts ComplexData input with line schema to LineParameter", function () { const parameter = wps.convertInputToParameter(wps, { Identifier: "geometry", ComplexData: { Default: { Format: { Schema: "http://geojson.org/geojson-spec.html#linestring" } } } }); expect(parameter).toEqual(jasmine.any(LineParameter)); }); it("converts ComplexData input with polygon schema to PolygonParameter", function () { const parameter = wps.convertInputToParameter(wps, { Identifier: "geometry", ComplexData: { Default: { Format: { Schema: "http://geojson.org/geojson-spec.html#polygon" } } } }); expect(parameter).toEqual(jasmine.any(PolygonParameter)); }); it("converts ComplexData input with GeoJson schema to GeoJsonParameter", function () { const parameter = wps.convertInputToParameter(wps, { Identifier: "geometry", ComplexData: { Default: { Format: { Schema: "http://geojson.org/geojson-spec.html#geojson" } } } }); expect(parameter).toEqual(jasmine.any(GeoJsonParameter)); }); it("converts input with BoundingBoxData to RectangleParameter", function () { const parameter = wps.convertInputToParameter(wps, { Identifier: "geometry", BoundingBoxData: { Default: { CRS: "crs84" } } }); expect(parameter).toEqual(jasmine.any(RectangleParameter)); }); }); it("can convert a parameter to data input", async function () { const parameter = new PointParameter(wps, { id: "foo" }); parameter.setValue(CommonStrata.user, Cartographic.ZERO); const input = await wps.convertParameterToInput(parameter); expect(input).toEqual({ inputIdentifier: "foo", inputValue: '{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[0,0,0]},"properties":{}}]}', inputType: "ComplexData" }); }); }); function initTerria() { const terria = new Terria({ baseUrl: "./" }); return terria; }