diff --git a/packages/downloads/src/download-status.ts b/packages/downloads/src/download-status.ts new file mode 100644 index 00000000000..8ed12db88de --- /dev/null +++ b/packages/downloads/src/download-status.ts @@ -0,0 +1,10 @@ +export type DownloadStatus = + | "ready" + | "ready_unknown" + | "stale" + | "not_ready" + | "creating" + | "updating" + | "error_creating" + | "error_updating" + | "error"; diff --git a/packages/downloads/src/hub/hub-poll-download-metadata.ts b/packages/downloads/src/hub/hub-poll-download-metadata.ts index e06e32a06bc..d2daf53fdb3 100644 --- a/packages/downloads/src/hub/hub-poll-download-metadata.ts +++ b/packages/downloads/src/hub/hub-poll-download-metadata.ts @@ -35,15 +35,18 @@ class HubPoller implements IPoller { } if (exportDatasetFailed(metadata)) { + const { + errors: [error] + } = metadata; eventEmitter.emit(`${downloadId}ExportError`, { - detail: { metadata } + detail: { error, metadata } }); return this.disablePoll(); } }) .catch(error => { eventEmitter.emit(`${downloadId}PollingError`, { - detail: { error } + detail: { error, metadata: { status: "error" } } }); return this.disablePoll(); }); diff --git a/packages/downloads/src/hub/hub-request-download-metadata.ts b/packages/downloads/src/hub/hub-request-download-metadata.ts index 3b02944eef9..8916e0e6f81 100644 --- a/packages/downloads/src/hub/hub-request-download-metadata.ts +++ b/packages/downloads/src/hub/hub-request-download-metadata.ts @@ -82,6 +82,7 @@ function formatApiResponse(json: any, downloadId: string) { contentLastModified, lastModified, status, + errors, contentLength, cacheTime, source: { lastEditDate } @@ -95,6 +96,7 @@ function formatApiResponse(json: any, downloadId: string) { lastEditDate, lastModified, status, + errors: errors || [], downloadUrl, contentLength, cacheTime diff --git a/packages/downloads/src/portal/portal-poll-export-job-status.ts b/packages/downloads/src/portal/portal-poll-export-job-status.ts index c635cfea3d4..c5ed30748ee 100644 --- a/packages/downloads/src/portal/portal-poll-export-job-status.ts +++ b/packages/downloads/src/portal/portal-poll-export-job-status.ts @@ -15,6 +15,11 @@ import { urlBuilder } from "../utils"; import { UserSession } from "@esri/arcgis-rest-auth"; import { IPoller } from "../poller"; +enum DownloadStatus { + READY = "ready", + ERROR = "error" +} + class ExportCompletionError extends Error { constructor(message: string) { /* istanbul ignore next */ @@ -87,7 +92,9 @@ class PortalPoller implements IPoller { if (metadata.status === "failed") { eventEmitter.emit(`${downloadId}ExportError`, { detail: { + error: new Error(metadata.statusMessage), metadata: { + status: DownloadStatus.ERROR, errors: [new Error(metadata.statusMessage)] } } @@ -98,11 +105,14 @@ class PortalPoller implements IPoller { .catch((error: any) => { if (error instanceof ExportCompletionError) { eventEmitter.emit(`${downloadId}ExportError`, { - detail: { metadata: { errors: [error] } } + detail: { + error, + metadata: { status: DownloadStatus.ERROR, errors: [error] } + } }); } else { eventEmitter.emit(`${downloadId}PollingError`, { - detail: { error } + detail: { error, metadata: { status: DownloadStatus.ERROR } } }); } return this.disablePoll(); @@ -173,7 +183,7 @@ function completedHandler(params: any): Promise { detail: { metadata: { downloadId, - status: "ready", + status: DownloadStatus.READY, lastModified: new Date().toISOString(), downloadUrl: urlBuilder({ host: authentication.portal, diff --git a/packages/downloads/src/request-download-metadata.ts b/packages/downloads/src/request-download-metadata.ts index e7281e1cf82..2dcc8ecc627 100644 --- a/packages/downloads/src/request-download-metadata.ts +++ b/packages/downloads/src/request-download-metadata.ts @@ -1,7 +1,8 @@ import { portalRequestDownloadMetadata } from "./portal/portal-request-download-metadata"; -import { hubRequestDownloadMetadata } from "./hub/hub-request-download-metadata" +import { hubRequestDownloadMetadata } from "./hub/hub-request-download-metadata"; import { DownloadFormat } from "./download-format"; -import { UserSession } from '@esri/arcgis-rest-auth'; +import { DownloadStatus } from "./download-status"; +import { UserSession } from "@esri/arcgis-rest-auth"; export interface IDownloadMetadataRequestParams { /* API target for downloads: 'hub' (default) or 'portal' */ @@ -24,28 +25,31 @@ export interface IDownloadMetadataRequestParams { export interface IDownloadMetadataResults { /* Identifier for the download */ - downloadId: string, + downloadId: string; - /* ready, not_ready, creating, updating, failed*/ - status: string, + /* ready, not_ready, creating, updating, failed */ + status: DownloadStatus; + + /* array of any errors related to exporting*/ + errors?: Error[]; /* ISO date of the service's last edit date */ - lastEditDate?:string, + lastEditDate?: string; /* ISO date of the download file's data - the last edit date of the service when the download export started */ - contentLastModified?: string, + contentLastModified?: string; /* File timestamp */ - lastModified?: string, + lastModified?: string; /* URL for downloading the file */ - downloadUrl?: string, + downloadUrl?: string; /* File size */ - contentLength?: number, + contentLength?: number; /* Time (milliseconds) it took to export and cache the download file */ - cacheTime?: number + cacheTime?: number; } /** @@ -89,7 +93,7 @@ export function requestDownloadMetadata( authentication } = params; - if (target === 'portal') { + if (target === "portal") { return portalRequestDownloadMetadata({ datasetId, format, diff --git a/packages/downloads/test/hub/hub-poll-download-metadata.test.ts b/packages/downloads/test/hub/hub-poll-download-metadata.test.ts index b23e312f1e6..136020484ec 100644 --- a/packages/downloads/test/hub/hub-poll-download-metadata.test.ts +++ b/packages/downloads/test/hub/hub-poll-download-metadata.test.ts @@ -17,6 +17,7 @@ const fixtures = { format: "CSV", status: "error_creating", featureSet: "full", + errors: [{ message: "Some error" }], source: { type: "Feature Service", url: @@ -77,20 +78,26 @@ describe("hubPollDownloadMetadata", () => { } ); const mockEventEmitter = new EventEmitter(); - spyOn(mockEventEmitter, 'emit'); + spyOn(mockEventEmitter, "emit"); const poller = hubPollDownloadMetadata({ - host: 'http://hub.com', - datasetId: 'abcdef0123456789abcdef0123456789_0', - downloadId: 'test-id', - spatialRefId: '4326', - format: 'CSV', + host: "http://hub.com", + datasetId: "abcdef0123456789abcdef0123456789_0", + downloadId: "test-id", + spatialRefId: "4326", + format: "CSV", eventEmitter: mockEventEmitter, pollingInterval: 10 }); await delay(100); expect(mockEventEmitter.emit as any).toHaveBeenCalledTimes(1); expect((mockEventEmitter.emit as any).calls.first().args).toEqual([ - 'test-idPollingError', { detail: { error: new Error('Bad Gateway') } } + "test-idPollingError", + { + detail: { + error: new Error("Bad Gateway"), + metadata: { status: "error" } + } + } ]); expect(poller.pollTimer).toEqual(null); } catch (err) { @@ -110,13 +117,13 @@ describe("hubPollDownloadMetadata", () => { } ); const mockEventEmitter = new EventEmitter(); - spyOn(mockEventEmitter, 'emit'); + spyOn(mockEventEmitter, "emit"); const poller = hubPollDownloadMetadata({ - host: 'http://hub.com', - datasetId: 'abcdef0123456789abcdef0123456789_0', - downloadId: 'test-id', - spatialRefId: '4326', - format: 'CSV', + host: "http://hub.com", + datasetId: "abcdef0123456789abcdef0123456789_0", + downloadId: "test-id", + spatialRefId: "4326", + format: "CSV", eventEmitter: mockEventEmitter, pollingInterval: 10 }); @@ -133,6 +140,7 @@ describe("hubPollDownloadMetadata", () => { contentLastModified: undefined, lastEditDate: "2020-06-18T01:17:31.492Z", lastModified: undefined, + errors: [{ message: "Some error" }], status: "error_creating", downloadUrl: "https://dev-hub-indexer.s3.amazonaws.com/files/dd4580c810204019a7b8eb3e0b329dd6/0/full/4326/dd4580c810204019a7b8eb3e0b329dd6_0_full_4326.csv", @@ -157,13 +165,13 @@ describe("hubPollDownloadMetadata", () => { } ); const mockEventEmitter = new EventEmitter(); - spyOn(mockEventEmitter, 'emit'); + spyOn(mockEventEmitter, "emit"); const poller = hubPollDownloadMetadata({ - host: 'http://hub.com', - datasetId: 'abcdef0123456789abcdef0123456789_0', - downloadId: 'test-id', - spatialRefId: '4326', - format: 'CSV', + host: "http://hub.com", + datasetId: "abcdef0123456789abcdef0123456789_0", + downloadId: "test-id", + spatialRefId: "4326", + format: "CSV", eventEmitter: mockEventEmitter, pollingInterval: 10 }); @@ -181,6 +189,7 @@ describe("hubPollDownloadMetadata", () => { lastEditDate: "2020-06-18T01:15:31.492Z", lastModified: "2020-06-17T13:04:28.000Z", status: "ready", + errors: [], downloadUrl: "https://dev-hub-indexer.s3.amazonaws.com/files/dd4580c810204019a7b8eb3e0b329dd6/0/full/4326/dd4580c810204019a7b8eb3e0b329dd6_0_full_4326.csv", contentLength: 1391454, @@ -233,13 +242,13 @@ describe("hubPollDownloadMetadata", () => { } ); const mockEventEmitter = new EventEmitter(); - spyOn(mockEventEmitter, 'emit'); + spyOn(mockEventEmitter, "emit"); const poller = hubPollDownloadMetadata({ - host: 'http://hub.com', - datasetId: 'abcdef0123456789abcdef0123456789_0', - downloadId: 'test-id', - spatialRefId: '4326', - format: 'CSV', + host: "http://hub.com", + datasetId: "abcdef0123456789abcdef0123456789_0", + downloadId: "test-id", + spatialRefId: "4326", + format: "CSV", eventEmitter: mockEventEmitter, pollingInterval: 10 }); diff --git a/packages/downloads/test/hub/hub-request-download-metadata.test.ts b/packages/downloads/test/hub/hub-request-download-metadata.test.ts index 3fe48bcf404..871d636b82c 100644 --- a/packages/downloads/test/hub/hub-request-download-metadata.test.ts +++ b/packages/downloads/test/hub/hub-request-download-metadata.test.ts @@ -195,6 +195,7 @@ describe("hubRequestDownloadMetadata", () => { lastEditDate: "2020-06-18T01:17:31.492Z", lastModified: "2020-06-17T13:04:28.000Z", status: "stale", + errors: [], downloadUrl: "https://dev-hub-indexer.s3.amazonaws.com/files/dd4580c810204019a7b8eb3e0b329dd6/0/full/4326/dd4580c810204019a7b8eb3e0b329dd6_0_full_4326.csv", contentLength: 1391454, @@ -231,6 +232,7 @@ describe("hubRequestDownloadMetadata", () => { lastEditDate: "2020-06-18T01:17:31.492Z", lastModified: "2020-06-17T13:04:28.000Z", status: "stale", + errors: [], downloadUrl: "https://dev-hub-indexer.s3.amazonaws.com/files/dd4580c810204019a7b8eb3e0b329dd6/0/full/4326/dd4580c810204019a7b8eb3e0b329dd6_0_full_4326.csv", contentLength: 1391454, @@ -276,6 +278,7 @@ describe("hubRequestDownloadMetadata", () => { lastEditDate: "2020-06-18T01:17:31.492Z", lastModified: "2020-06-17T13:04:28.000Z", status: "stale", + errors: [], downloadUrl: "https://dev-hub-indexer.s3.amazonaws.com/files/dd4580c810204019a7b8eb3e0b329dd6/0/full/4326/dd4580c810204019a7b8eb3e0b329dd6_0_full_4326.csv", contentLength: 1391454, diff --git a/packages/downloads/test/portal/portal-poll-export-job-status.test.ts b/packages/downloads/test/portal/portal-poll-export-job-status.test.ts index be13b054b17..b222602b5e8 100644 --- a/packages/downloads/test/portal/portal-poll-export-job-status.test.ts +++ b/packages/downloads/test/portal/portal-poll-export-job-status.test.ts @@ -39,12 +39,12 @@ describe("portalPollExportJobStatus", () => { ); const mockEventEmitter = new EventEmitter(); - spyOn(mockEventEmitter, 'emit'); + spyOn(mockEventEmitter, "emit"); const poller = portalPollExportJobStatus({ - downloadId: 'download-id', - jobId: 'test-id', - datasetId: 'abcdef0123456789abcdef0123456789_0', - format: 'CSV', + downloadId: "download-id", + jobId: "test-id", + datasetId: "abcdef0123456789abcdef0123456789_0", + format: "CSV", authentication, exportCreated: 1000, pollingInterval: 10, @@ -66,9 +66,8 @@ describe("portalPollExportJobStatus", () => { "download-idExportError", { detail: { - metadata: { - errors: [new Error("Export failed")] - } + error: new Error("Export failed"), + metadata: { status: "error", errors: [new Error("Export failed")] } } } ]); @@ -82,17 +81,17 @@ describe("portalPollExportJobStatus", () => { it("handle polling error", async done => { try { - spyOn(portal, 'getItemStatus').and.callFake(async () => { - return Promise.reject(new Error('Not Found')) - }) + spyOn(portal, "getItemStatus").and.callFake(async () => { + return Promise.reject(new Error("Not Found")); + }); const mockEventEmitter = new EventEmitter(); - spyOn(mockEventEmitter, 'emit'); + spyOn(mockEventEmitter, "emit"); const poller = portalPollExportJobStatus({ - downloadId: 'download-id', - jobId: 'test-id', - datasetId: 'abcdef0123456789abcdef0123456789_0', - format: 'CSV', + downloadId: "download-id", + jobId: "test-id", + datasetId: "abcdef0123456789abcdef0123456789_0", + format: "CSV", authentication, exportCreated: 1000, pollingInterval: 10, @@ -115,10 +114,11 @@ describe("portalPollExportJobStatus", () => { "download-idPollingError", { detail: { - error: new Error("Not Found") + error: new Error("Not Found"), + metadata: { status: "error" } } } - ]) + ]); expect(poller.pollTimer === null).toEqual(true); } catch (err) { expect(err).toEqual(undefined); @@ -130,25 +130,25 @@ describe("portalPollExportJobStatus", () => { describe("export-completed handling errors", () => { it("updateItem failure", async done => { try { - spyOn(portal, 'getItemStatus').and.callFake( async () => { - return Promise.resolve({ status: 'completed' }) + spyOn(portal, "getItemStatus").and.callFake(async () => { + return Promise.resolve({ status: "completed" }); }); - - spyOn(portal, 'updateItem').and.callFake( async () => { - return Promise.reject(new Error('5xx')) + + spyOn(portal, "updateItem").and.callFake(async () => { + return Promise.reject(new Error("5xx")); }); - spyOn(portal, 'removeItem').and.callFake( async () => { + spyOn(portal, "removeItem").and.callFake(async () => { return Promise.resolve(); }); const mockEventEmitter = new EventEmitter(); - spyOn(mockEventEmitter, 'emit'); + spyOn(mockEventEmitter, "emit"); const poller = portalPollExportJobStatus({ - downloadId: 'download-id', - jobId: 'test-id', - datasetId: 'abcdef0123456789abcdef0123456789_0', - format: 'CSV', + downloadId: "download-id", + jobId: "test-id", + datasetId: "abcdef0123456789abcdef0123456789_0", + format: "CSV", authentication, exportCreated: 1000, pollingInterval: 10, @@ -187,10 +187,11 @@ describe("portalPollExportJobStatus", () => { "download-idExportError", { detail: { - metadata: { errors: [new Error("5xx")] } + error: new Error("5xx"), + metadata: { status: "error", errors: [new Error("5xx")] } } } - ]) + ]); expect(poller.pollTimer === null).toEqual(true); } catch (err) { expect(err).toEqual(undefined); @@ -201,22 +202,22 @@ describe("portalPollExportJobStatus", () => { it("setItemAccess failure", async done => { try { - spyOn(portal, 'getItemStatus').and.callFake(async () => { - return Promise.resolve({ status: 'completed' }); + spyOn(portal, "getItemStatus").and.callFake(async () => { + return Promise.resolve({ status: "completed" }); }); - spyOn(portal, 'updateItem').and.callFake(async () => { + spyOn(portal, "updateItem").and.callFake(async () => { return Promise.resolve(); }); - - spyOn(portal, 'setItemAccess').and.callFake(async () => { - return Promise.reject(new Error('5xx')); + + spyOn(portal, "setItemAccess").and.callFake(async () => { + return Promise.reject(new Error("5xx")); }); - - spyOn(portal, 'removeItem').and.callFake(async () => { + + spyOn(portal, "removeItem").and.callFake(async () => { return Promise.resolve(); }); - + const mockEventEmitter = new EventEmitter(); spyOn(mockEventEmitter, "emit"); portalPollExportJobStatus({ @@ -229,7 +230,7 @@ describe("portalPollExportJobStatus", () => { pollingInterval: 10, eventEmitter: mockEventEmitter }); - + await delay(100); expect(portal.getItemStatus).toHaveBeenCalledTimes(1); expect((portal.getItemStatus as any).calls.first().args).toEqual([ @@ -270,7 +271,8 @@ describe("portalPollExportJobStatus", () => { "download-idExportError", { detail: { - metadata: { errors: [new Error("5xx")] } + error: new Error("5xx"), + metadata: { status: "error", errors: [new Error("5xx")] } } } ]); @@ -283,33 +285,33 @@ describe("portalPollExportJobStatus", () => { it("userContent failure", async done => { try { - spyOn(portal, 'getItemStatus').and.callFake(async () => { - return Promise.resolve({ status: 'completed' }); + spyOn(portal, "getItemStatus").and.callFake(async () => { + return Promise.resolve({ status: "completed" }); }); - - spyOn(portal, 'updateItem').and.callFake(async () => { + + spyOn(portal, "updateItem").and.callFake(async () => { return Promise.resolve(); }); - - spyOn(portal, 'setItemAccess').and.callFake(async () => { + + spyOn(portal, "setItemAccess").and.callFake(async () => { return Promise.resolve(); }); - - spyOn(portal, 'getUserContent').and.callFake(async () => { - return Promise.reject(new Error('5xx')); + + spyOn(portal, "getUserContent").and.callFake(async () => { + return Promise.reject(new Error("5xx")); }); - spyOn(portal, 'removeItem').and.callFake(async () => { + spyOn(portal, "removeItem").and.callFake(async () => { return Promise.resolve(); }); const mockEventEmitter = new EventEmitter(); - spyOn(mockEventEmitter, 'emit'); + spyOn(mockEventEmitter, "emit"); const poller = portalPollExportJobStatus({ - downloadId: 'download-id', - jobId: 'test-id', - datasetId: 'abcdef0123456789abcdef0123456789_0', - format: 'CSV', + downloadId: "download-id", + jobId: "test-id", + datasetId: "abcdef0123456789abcdef0123456789_0", + format: "CSV", authentication, exportCreated: 1000, pollingInterval: 10, @@ -360,7 +362,8 @@ describe("portalPollExportJobStatus", () => { "download-idExportError", { detail: { - metadata: { errors: [new Error("5xx")] } + error: new Error("5xx"), + metadata: { status: "error", errors: [new Error("5xx")] } } } ]); @@ -374,37 +377,37 @@ describe("portalPollExportJobStatus", () => { it("createFolder failure", async done => { try { - spyOn(portal, 'getItemStatus').and.callFake(async () => { - return Promise.resolve({ status: 'completed' }); + spyOn(portal, "getItemStatus").and.callFake(async () => { + return Promise.resolve({ status: "completed" }); }); - - spyOn(portal, 'updateItem').and.callFake(async () => { + + spyOn(portal, "updateItem").and.callFake(async () => { return Promise.resolve(); }); - - spyOn(portal, 'setItemAccess').and.callFake(async () => { + + spyOn(portal, "setItemAccess").and.callFake(async () => { return Promise.resolve(); }); - - spyOn(portal, 'getUserContent').and.callFake(async () => { + + spyOn(portal, "getUserContent").and.callFake(async () => { return Promise.resolve({ folders: [] }); }); - spyOn(portal, 'createFolder').and.callFake(async () => { - return Promise.reject(new Error('5xx')); + spyOn(portal, "createFolder").and.callFake(async () => { + return Promise.reject(new Error("5xx")); }); - spyOn(portal, 'removeItem').and.callFake(async () => { + spyOn(portal, "removeItem").and.callFake(async () => { return Promise.resolve(); }); const mockEventEmitter = new EventEmitter(); - spyOn(mockEventEmitter, 'emit'); + spyOn(mockEventEmitter, "emit"); const poller = portalPollExportJobStatus({ - downloadId: 'download-id', - jobId: 'test-id', - datasetId: 'abcdef0123456789abcdef0123456789_0', - format: 'CSV', + downloadId: "download-id", + jobId: "test-id", + datasetId: "abcdef0123456789abcdef0123456789_0", + format: "CSV", authentication, exportCreated: 1000, pollingInterval: 10, @@ -462,7 +465,8 @@ describe("portalPollExportJobStatus", () => { "download-idExportError", { detail: { - metadata: { errors: [new Error("5xx")] } + error: new Error("5xx"), + metadata: { status: "error", errors: [new Error("5xx")] } } } ]); @@ -476,43 +480,40 @@ describe("portalPollExportJobStatus", () => { it("moveItem failure", async done => { try { - spyOn(portal, 'getItemStatus').and.callFake(async () => { - return Promise.resolve({ status: 'completed' }); + spyOn(portal, "getItemStatus").and.callFake(async () => { + return Promise.resolve({ status: "completed" }); }); - - spyOn(portal, 'updateItem').and.callFake(async () => { + + spyOn(portal, "updateItem").and.callFake(async () => { return Promise.resolve(); }); - spyOn(portal, 'setItemAccess').and.callFake(async () => { + spyOn(portal, "setItemAccess").and.callFake(async () => { return Promise.resolve(); }); - spyOn(portal, 'getUserContent').and.callFake(async () => { + spyOn(portal, "getUserContent").and.callFake(async () => { return Promise.resolve({ folders: [] }); }); - spyOn(portal, 'createFolder').and.callFake(async () => { - return Promise.resolve({ folder: { id: 'export-folder-id' } }); + spyOn(portal, "createFolder").and.callFake(async () => { + return Promise.resolve({ folder: { id: "export-folder-id" } }); }); -; - - spyOn(portal, 'moveItem').and.callFake(async () => { - return Promise.reject(new Error('5xx')); + spyOn(portal, "moveItem").and.callFake(async () => { + return Promise.reject(new Error("5xx")); }); - - spyOn(portal, 'removeItem').and.callFake(async () => { + spyOn(portal, "removeItem").and.callFake(async () => { return Promise.resolve(); }); const mockEventEmitter = new EventEmitter(); - spyOn(mockEventEmitter, 'emit'); + spyOn(mockEventEmitter, "emit"); const poller = portalPollExportJobStatus({ - downloadId: 'download-id', - jobId: 'test-id', - datasetId: 'abcdef0123456789abcdef0123456789_0', - format: 'CSV', + downloadId: "download-id", + jobId: "test-id", + datasetId: "abcdef0123456789abcdef0123456789_0", + format: "CSV", authentication, exportCreated: 1000, pollingInterval: 10, @@ -578,7 +579,8 @@ describe("portalPollExportJobStatus", () => { "download-idExportError", { detail: { - metadata: { errors: [new Error("5xx")] } + error: new Error("5xx"), + metadata: { status: "error", errors: [new Error("5xx")] } } } ]); @@ -606,37 +608,37 @@ describe("portalPollExportJobStatus", () => { }); }) ); - - spyOn(portal, 'updateItem').and.callFake(async () => { + + spyOn(portal, "updateItem").and.callFake(async () => { return Promise.resolve(); }); - - spyOn(portal, 'setItemAccess').and.callFake(async () => { + + spyOn(portal, "setItemAccess").and.callFake(async () => { return Promise.resolve(); }); - spyOn(portal, 'getUserContent').and.callFake(async () => { + spyOn(portal, "getUserContent").and.callFake(async () => { return Promise.resolve({ folders: [] }); }); - spyOn(portal, 'createFolder').and.callFake(async () => { - return Promise.resolve({ folder: { id: 'export-folder-id' } }); - });; + spyOn(portal, "createFolder").and.callFake(async () => { + return Promise.resolve({ folder: { id: "export-folder-id" } }); + }); - spyOn(portal, 'moveItem').and.callFake(async () => { + spyOn(portal, "moveItem").and.callFake(async () => { return Promise.resolve(); }); - spyOn(portal, 'removeItem').and.callFake(async () => { + spyOn(portal, "removeItem").and.callFake(async () => { return Promise.resolve(); }); const mockEventEmitter = new EventEmitter(); - spyOn(mockEventEmitter, 'emit'); + spyOn(mockEventEmitter, "emit"); const poller = portalPollExportJobStatus({ - downloadId: 'download-id', - jobId: 'test-id', - datasetId: 'abcdef0123456789abcdef0123456789_0', - format: 'CSV', + downloadId: "download-id", + jobId: "test-id", + datasetId: "abcdef0123456789abcdef0123456789_0", + format: "CSV", authentication, exportCreated: 1000, pollingInterval: 10, @@ -700,11 +702,17 @@ describe("portalPollExportJobStatus", () => { detail: { metadata: { downloadId, status, downloadUrl, lastModified } } - } = (mockEventEmitter.emit as any).calls.first().args[1] - expect(downloadId).toEqual('download-id'); - expect(status).toEqual('ready'); - expect(downloadUrl).toEqual('http://portal.com/sharing/rest/content/items/download-id/data?token=123'); - expect(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}Z$/.test(lastModified)).toEqual(true); + } = (mockEventEmitter.emit as any).calls.first().args[1]; + expect(downloadId).toEqual("download-id"); + expect(status).toEqual("ready"); + expect(downloadUrl).toEqual( + "http://portal.com/sharing/rest/content/items/download-id/data?token=123" + ); + expect( + /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}Z$/.test( + lastModified + ) + ).toEqual(true); expect(poller.pollTimer === null).toEqual(true); } catch (err) { expect(err).toEqual(undefined); @@ -767,12 +775,12 @@ describe("portalPollExportJobStatus", () => { }) ); const mockEventEmitter = new EventEmitter(); - spyOn(mockEventEmitter, 'emit'); + spyOn(mockEventEmitter, "emit"); const poller = portalPollExportJobStatus({ - downloadId: 'download-id', - jobId: 'test-id', - datasetId: 'abcdef0123456789abcdef0123456789_0', - format: 'CSV', + downloadId: "download-id", + jobId: "test-id", + datasetId: "abcdef0123456789abcdef0123456789_0", + format: "CSV", authentication, exportCreated: 1000, pollingInterval: 10, @@ -831,11 +839,17 @@ describe("portalPollExportJobStatus", () => { detail: { metadata: { downloadId, status, downloadUrl, lastModified } } - } = (mockEventEmitter.emit as any).calls.first().args[1] - expect(downloadId).toEqual('download-id'); - expect(status).toEqual('ready'); - expect(downloadUrl).toEqual('http://portal.com/sharing/rest/content/items/download-id/data?token=123'); - expect(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}Z$/.test(lastModified)).toEqual(true); + } = (mockEventEmitter.emit as any).calls.first().args[1]; + expect(downloadId).toEqual("download-id"); + expect(status).toEqual("ready"); + expect(downloadUrl).toEqual( + "http://portal.com/sharing/rest/content/items/download-id/data?token=123" + ); + expect( + /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}Z$/.test( + lastModified + ) + ).toEqual(true); expect(poller.pollTimer === null).toEqual(true); } catch (err) { expect(err).toEqual(undefined); @@ -846,47 +860,47 @@ describe("portalPollExportJobStatus", () => { it("succeeds without moving download", async done => { try { - spyOn(portal, 'getItemStatus').and.callFake(async () => { - return Promise.resolve({ status: 'completed' }); + spyOn(portal, "getItemStatus").and.callFake(async () => { + return Promise.resolve({ status: "completed" }); }); - - spyOn(portal, 'updateItem').and.callFake(async () => { + + spyOn(portal, "updateItem").and.callFake(async () => { return Promise.resolve(); }); - - spyOn(portal, 'setItemAccess').and.callFake(async () => { + + spyOn(portal, "setItemAccess").and.callFake(async () => { return Promise.resolve(); }); - spyOn(portal, 'getUserContent').and.callFake(async () => { - return Promise.resolve({ folders: []}); + spyOn(portal, "getUserContent").and.callFake(async () => { + return Promise.resolve({ folders: [] }); }); - spyOn(portal, 'createFolder').and.callFake(async () => { - return Promise.resolve({ folder: { id: 'export-folder-id' } }); + spyOn(portal, "createFolder").and.callFake(async () => { + return Promise.resolve({ folder: { id: "export-folder-id" } }); }); - spyOn(portal, 'moveItem').and.callFake(async () => { - return Promise.reject(new RestJsError('Already moved', 'CONT_0011')); + spyOn(portal, "moveItem").and.callFake(async () => { + return Promise.reject(new RestJsError("Already moved", "CONT_0011")); }); - spyOn(portal, 'removeItem').and.callFake(async () => { + spyOn(portal, "removeItem").and.callFake(async () => { return Promise.resolve(); }); const mockEventEmitter = new EventEmitter(); - spyOn(mockEventEmitter, 'emit'); + spyOn(mockEventEmitter, "emit"); const poller = portalPollExportJobStatus({ - downloadId: 'download-id', - jobId: 'test-id', - datasetId: 'abcdef0123456789abcdef0123456789_0', - format: 'CSV', + downloadId: "download-id", + jobId: "test-id", + datasetId: "abcdef0123456789abcdef0123456789_0", + format: "CSV", authentication, exportCreated: 1000, pollingInterval: 10, eventEmitter: mockEventEmitter }); - + expect(poller.pollTimer !== null).toEqual(true); await delay(100); expect(portal.getItemStatus).toHaveBeenCalledTimes(1); @@ -944,11 +958,17 @@ describe("portalPollExportJobStatus", () => { detail: { metadata: { downloadId, status, downloadUrl, lastModified } } - } = (mockEventEmitter.emit as any).calls.first().args[1] - expect(downloadId).toEqual('download-id'); - expect(status).toEqual('ready'); - expect(downloadUrl).toEqual('http://portal.com/sharing/rest/content/items/download-id/data?token=123'); - expect(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}Z$/.test(lastModified)).toEqual(true); + } = (mockEventEmitter.emit as any).calls.first().args[1]; + expect(downloadId).toEqual("download-id"); + expect(status).toEqual("ready"); + expect(downloadUrl).toEqual( + "http://portal.com/sharing/rest/content/items/download-id/data?token=123" + ); + expect( + /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}Z$/.test( + lastModified + ) + ).toEqual(true); expect(poller.pollTimer === null).toEqual(true); } catch (err) { expect(err).toEqual(undefined); @@ -1008,19 +1028,19 @@ describe("portalPollExportJobStatus", () => { }) ); const mockEventEmitter = new EventEmitter(); - spyOn(mockEventEmitter, 'emit'); + spyOn(mockEventEmitter, "emit"); const poller = portalPollExportJobStatus({ - downloadId: 'download-id', - jobId: 'test-id', - datasetId: 'abcdef0123456789abcdef0123456789_0', - format: 'CSV', - spatialRefId: '4326', + downloadId: "download-id", + jobId: "test-id", + datasetId: "abcdef0123456789abcdef0123456789_0", + format: "CSV", + spatialRefId: "4326", authentication, exportCreated: 1000, pollingInterval: 10, eventEmitter: mockEventEmitter }); - + expect(poller.pollTimer !== null).toEqual(true); await delay(100); expect(portal.getItemStatus).toHaveBeenCalledTimes(2); @@ -1079,11 +1099,17 @@ describe("portalPollExportJobStatus", () => { detail: { metadata: { downloadId, status, downloadUrl, lastModified } } - } = (mockEventEmitter.emit as any).calls.first().args[1] - expect(downloadId).toEqual('download-id'); - expect(status).toEqual('ready'); - expect(downloadUrl).toEqual('http://portal.com/sharing/rest/content/items/download-id/data?token=123'); - expect(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}Z$/.test(lastModified)).toEqual(true); + } = (mockEventEmitter.emit as any).calls.first().args[1]; + expect(downloadId).toEqual("download-id"); + expect(status).toEqual("ready"); + expect(downloadUrl).toEqual( + "http://portal.com/sharing/rest/content/items/download-id/data?token=123" + ); + expect( + /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}Z$/.test( + lastModified + ) + ).toEqual(true); expect(poller.pollTimer === null).toEqual(true); } catch (err) { expect(err).toEqual(undefined); diff --git a/packages/downloads/test/request-download-metadata.test.ts b/packages/downloads/test/request-download-metadata.test.ts index bedb7c108b5..f9e424913f2 100644 --- a/packages/downloads/test/request-download-metadata.test.ts +++ b/packages/downloads/test/request-download-metadata.test.ts @@ -1,60 +1,66 @@ - -import * as fetchMock from 'fetch-mock'; -import { UserSession } from '@esri/arcgis-rest-auth'; +import * as fetchMock from "fetch-mock"; +import { UserSession } from "@esri/arcgis-rest-auth"; import { requestDownloadMetadata } from "../src/request-download-metadata"; describe("requestDownloadMetadata", () => { - afterEach(() => fetchMock.restore()); - it('handle hub download', async done => { + it("handle hub download", async done => { try { - fetchMock.mock('http://hub.com/api/v3/datasets/abcdef0123456789abcdef0123456789_0/downloads?spatialRefId=4326&formats=csv', { - status: 200, - body: { - data: [ - { - id: 'dd4580c810204019a7b8eb3e0b329dd6_0', - type: 'downloads', - attributes: { - spatialRefId: '4326', - format: 'CSV', - contentLength: 1391454, - lastModified: '2020-06-17T13:04:28.000Z', - contentLastModified: '2020-06-17T01:16:01.933Z', - cacheTime: 13121, - status: 'stale', - featureSet: 'full', - source: { - type: 'Feature Service', - url: 'https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_COVID19/FeatureServer/0?f=json', - supportsExtract: true, - lastEditDate: '2020-06-18T01:17:31.492Z', - spatialRefId: '4326' + fetchMock.mock( + "http://hub.com/api/v3/datasets/abcdef0123456789abcdef0123456789_0/downloads?spatialRefId=4326&formats=csv", + { + status: 200, + body: { + data: [ + { + id: "dd4580c810204019a7b8eb3e0b329dd6_0", + type: "downloads", + attributes: { + spatialRefId: "4326", + format: "CSV", + contentLength: 1391454, + lastModified: "2020-06-17T13:04:28.000Z", + contentLastModified: "2020-06-17T01:16:01.933Z", + cacheTime: 13121, + status: "stale", + featureSet: "full", + source: { + type: "Feature Service", + url: + "https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_COVID19/FeatureServer/0?f=json", + supportsExtract: true, + lastEditDate: "2020-06-18T01:17:31.492Z", + spatialRefId: "4326" + } + }, + links: { + content: + "https://dev-hub-indexer.s3.amazonaws.com/files/dd4580c810204019a7b8eb3e0b329dd6/0/full/4326/dd4580c810204019a7b8eb3e0b329dd6_0_full_4326.csv" } - }, - links: { - content: 'https://dev-hub-indexer.s3.amazonaws.com/files/dd4580c810204019a7b8eb3e0b329dd6/0/full/4326/dd4580c810204019a7b8eb3e0b329dd6_0_full_4326.csv' } - } - ] + ] + } } - }); + ); const result = await requestDownloadMetadata({ - host: 'http://hub.com/', - datasetId: 'abcdef0123456789abcdef0123456789_0', - spatialRefId: '4326', - format: 'CSV' + host: "http://hub.com/", + datasetId: "abcdef0123456789abcdef0123456789_0", + spatialRefId: "4326", + format: "CSV" }); expect(result).toEqual({ - downloadId: 'abcdef0123456789abcdef0123456789_0:CSV:4326:undefined:undefined', - contentLastModified: '2020-06-17T01:16:01.933Z', - lastEditDate: '2020-06-18T01:17:31.492Z', - lastModified: '2020-06-17T13:04:28.000Z', - status: 'stale', - downloadUrl: 'https://dev-hub-indexer.s3.amazonaws.com/files/dd4580c810204019a7b8eb3e0b329dd6/0/full/4326/dd4580c810204019a7b8eb3e0b329dd6_0_full_4326.csv', + downloadId: + "abcdef0123456789abcdef0123456789_0:CSV:4326:undefined:undefined", + contentLastModified: "2020-06-17T01:16:01.933Z", + lastEditDate: "2020-06-18T01:17:31.492Z", + lastModified: "2020-06-17T13:04:28.000Z", + status: "stale", + errors: [], + downloadUrl: + "https://dev-hub-indexer.s3.amazonaws.com/files/dd4580c810204019a7b8eb3e0b329dd6/0/full/4326/dd4580c810204019a7b8eb3e0b329dd6_0_full_4326.csv", contentLength: 1391454, cacheTime: 13121 }); @@ -65,51 +71,59 @@ describe("requestDownloadMetadata", () => { } }); - it('handle portal download', async done => { + it("handle portal download", async done => { const authentication = new UserSession({ - username: 'portal-user', + username: "portal-user", portal: `http://portal.com/sharing/rest`, - token: '123', - }) - authentication.getToken = () => new Promise((resolve) => { - resolve('123') - }) - + token: "123" + }); + authentication.getToken = () => + new Promise(resolve => { + resolve("123"); + }); + try { - fetchMock.mock('http://portal.com/sharing/rest/content/items/abcdef0123456789abcdef0123456789?f=json&token=123', { - status: 200, - body: { - type: 'Feature Service', - created: 1592360698651, - modified: 1592360698651, - url:'https://feature-service.com/FeatureServer' + fetchMock.mock( + "http://portal.com/sharing/rest/content/items/abcdef0123456789abcdef0123456789?f=json&token=123", + { + status: 200, + body: { + type: "Feature Service", + created: 1592360698651, + modified: 1592360698651, + url: "https://feature-service.com/FeatureServer" + } } - }); + ); - fetchMock.post('https://feature-service.com/FeatureServer/0', { + fetchMock.post("https://feature-service.com/FeatureServer/0", { status: 200, body: {} }); - fetchMock.mock('http://portal.com/sharing/rest/search?f=json&q=type%3A%22Shapefile%22%20AND%20typekeywords%3A%22export%3Aabcdef0123456789abcdef0123456789%2CspatialRefId%3A2227%22&num=1&sortField=modified&sortOrder=DESC&token=123', { - status: 200, - body: { - results: [] + fetchMock.mock( + "http://portal.com/sharing/rest/search?f=json&q=type%3A%22Shapefile%22%20AND%20typekeywords%3A%22export%3Aabcdef0123456789abcdef0123456789%2CspatialRefId%3A2227%22&num=1&sortField=modified&sortOrder=DESC&token=123", + { + status: 200, + body: { + results: [] + } } - }); + ); const result = await requestDownloadMetadata({ - datasetId: 'abcdef0123456789abcdef0123456789', - format: 'Shapefile', + datasetId: "abcdef0123456789abcdef0123456789", + format: "Shapefile", authentication, - target: 'portal', - spatialRefId: '2227' + target: "portal", + spatialRefId: "2227" }); expect(result).toEqual({ - downloadId: 'abcdef0123456789abcdef0123456789:Shapefile:2227:undefined:undefined', + downloadId: + "abcdef0123456789abcdef0123456789:Shapefile:2227:undefined:undefined", lastEditDate: undefined, - status: 'not_ready', + status: "not_ready" }); } catch (err) { expect(err).toEqual(undefined); @@ -117,4 +131,4 @@ describe("requestDownloadMetadata", () => { done(); } }); -}); \ No newline at end of file +});