Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
266 changes: 266 additions & 0 deletions __tests__/components/exportToPlatform.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
import type {
FileLocation,
UseRequestFileLocationResult,
} from "@databiosphere/findable-ui/lib/hooks/useRequestFileLocation";
import type { FileManifestState } from "@databiosphere/findable-ui/lib/providers/fileManifestState";
import { createAppTheme } from "@databiosphere/findable-ui/lib/theme/theme";
import { ThemeProvider } from "@mui/material";
import { render, screen } from "@testing-library/react";
import { ExportToPlatform } from "../../app/components/Export/components/AnVILExplorer/platform/ExportToPlatform/exportToPlatform";
import type {
PlatformImport,
Props,
} from "../../app/components/Export/components/AnVILExplorer/platform/ExportToPlatform/types";
import {
buildPlatformImportUrl,
getPlatformImportUrl,
isPlatformImportAvailable,
} from "../../app/components/Export/components/AnVILExplorer/platform/ExportToPlatform/utils";
import { FEATURES } from "../../app/shared/entities";

const mockUseRequestFileLocation = jest.fn<UseRequestFileLocationResult, []>();

jest.mock("@databiosphere/findable-ui/lib/hooks/useDownloadStatus", () => ({
useDownloadStatus: (): {
disabled: boolean;
isLoading: boolean;
message: string;
} => ({ disabled: false, isLoading: false, message: "" }),
}));

jest.mock(
"@databiosphere/findable-ui/lib/hooks/useFileManifest/useFileManifest",
() => ({ useFileManifest: jest.fn() })
);

jest.mock(
"@databiosphere/findable-ui/lib/hooks/useFileManifest/useFileManifestFileCount",
() => ({ useFileManifestFileCount: jest.fn() })
);

jest.mock("@databiosphere/findable-ui/lib/hooks/useFileManifestState", () => ({
useFileManifestState: (): {
fileManifestDispatch: jest.Mock;
fileManifestState: { isLoading: boolean };
} => ({
fileManifestDispatch: jest.fn(),
fileManifestState: { isLoading: false },
}),
}));

jest.mock(
"@databiosphere/findable-ui/lib/hooks/useRequestFileLocation",
() => ({
useRequestFileLocation: (): UseRequestFileLocationResult =>
mockUseRequestFileLocation(),
})
);

jest.mock(
"@databiosphere/findable-ui/lib/hooks/useRequestManifest/useRequestManifest",
() => ({
useRequestManifest: (): { requestMethod: string; requestUrl: string } => ({
requestMethod: "PUT",
requestUrl: "https://service.example.org/manifest/files",
}),
})
);

jest.mock("@databiosphere/findable-ui/lib/providers/loginGuard/hook", () => ({
useLoginGuard: (): { requireLogin: jest.Mock } => ({
requireLogin: jest.fn(),
}),
}));

const PLATFORM_IMPORT: PlatformImport = {
baseUrl: "https://platform.sb.biodatacatalyst.nhlbi.nih.gov/import/pfb",
source: "bdc",
};

const SIGNED_URL =
"https://storage.googleapis.com/bucket/manifest.avro?X-Goog-Signature=a%2Fb&X-Goog-Expires=3600";

const IMPORT_URL =
"https://platform.sb.biodatacatalyst.nhlbi.nih.gov/import/pfb?URL=https%3A%2F%2Fstorage.googleapis.com%2Fbucket%2Fmanifest.avro%3FX-Goog-Signature%3Da%252Fb%26X-Goog-Expires%3D3600&source=bdc";

const FILE_LOCATION: FileLocation = { location: SIGNED_URL, status: 302 };

describe("buildPlatformImportUrl", () => {
it("encodes the signed URL and appends the source", () => {
expect(buildPlatformImportUrl(PLATFORM_IMPORT, SIGNED_URL)).toBe(
IMPORT_URL
);
});

it("round-trips the signed URL through the URL parameter", () => {
const url = new URL(buildPlatformImportUrl(PLATFORM_IMPORT, SIGNED_URL));
expect(url.searchParams.get("URL")).toBe(SIGNED_URL);
expect(url.searchParams.get("source")).toBe("bdc");
});
});

describe("getPlatformImportUrl", () => {
it("returns the import URL when enabled with a config and file location", () => {
expect(getPlatformImportUrl(true, PLATFORM_IMPORT, FILE_LOCATION)).toBe(
IMPORT_URL
);
});

it("returns undefined when the feature flag is off", () => {
expect(
getPlatformImportUrl(false, PLATFORM_IMPORT, FILE_LOCATION)
).toBeUndefined();
});

it("returns undefined when the platform has no import config", () => {
expect(
getPlatformImportUrl(true, undefined, FILE_LOCATION)
).toBeUndefined();
});

it("returns undefined before the file location is available", () => {
expect(
getPlatformImportUrl(true, PLATFORM_IMPORT, undefined)
).toBeUndefined();
});
});

describe("isPlatformImportAvailable", () => {
it("returns true when enabled with a config", () => {
expect(isPlatformImportAvailable(true, PLATFORM_IMPORT)).toBe(true);
});

it("returns false when the feature flag is off", () => {
expect(isPlatformImportAvailable(false, PLATFORM_IMPORT)).toBe(false);
});

it("returns false when the platform has no import config", () => {
expect(isPlatformImportAvailable(true, undefined)).toBe(false);
});
});

const BUTTON_LABEL = "Open BioData Catalyst";

const REQUEST_LINK = "Request Link";

const FILE_MANIFEST_STATE: FileManifestState = {
fileCount: undefined,
fileSummary: undefined,
fileSummaryFacetName: undefined,
fileSummaryFilters: [],
filesFacets: [],
filters: [],
isEnabled: true,
isFacetsLoading: false,
isFacetsSuccess: true,
isFileSummaryLoading: false,
isLoading: false,
isSummaryLoading: false,
summary: undefined,
};

const PROPS: Props = {
buttonLabel: BUTTON_LABEL,
description: "BDC-SB is a cloud workspace.",
fileManifestState: FILE_MANIFEST_STATE,
fileSummaryFacetName: "files.file_format",
filters: [],
formFacet: {},
platformImport: PLATFORM_IMPORT,
speciesFacetName: "donors.organism_type",
successTitle: "Your BioData Catalyst Workspace Link is Ready",
title: "Analyze in BioData Catalyst",
};

const THEME = createAppTheme();

/**
* Builds a useRequestFileLocation result.
* @param data - File location returned by the manifest request.
* @returns useRequestFileLocation result.
*/
function buildFileLocationResult(
data: FileLocation | undefined
): UseRequestFileLocationResult {
return {
data,
isIdle: !data,
isLoading: false,
isSuccess: !!data,
run: jest.fn(),
};
}

/**
* Returns the "Request Link" button.
* @returns "Request Link" button element.
*/
function getRequestLinkButton(): HTMLElement {
return screen.getByRole("button", { name: REQUEST_LINK });
}

/**
* Renders ExportToPlatform within the app theme.
* @param props - Export to platform props.
*/
function renderExportToPlatform(props: Props): void {
render(
<ThemeProvider theme={THEME}>
<ExportToPlatform {...props} />
</ThemeProvider>
);
}

describe("ExportToPlatform", () => {
afterEach(() => {
localStorage.clear();
mockUseRequestFileLocation.mockReset();
});

it("disables Request Link when the Velsera export flag is off", () => {
mockUseRequestFileLocation.mockReturnValue(
buildFileLocationResult(undefined)
);
renderExportToPlatform(PROPS);
expect(getRequestLinkButton().hasAttribute("disabled")).toBe(true);
});

it("disables Request Link when the platform has no import endpoint", () => {
localStorage.setItem(FEATURES.VELSERA_EXPORT, "true");
mockUseRequestFileLocation.mockReturnValue(
buildFileLocationResult(undefined)
);
renderExportToPlatform({ ...PROPS, platformImport: undefined });
expect(getRequestLinkButton().hasAttribute("disabled")).toBe(true);
});

it("enables Request Link when the Velsera export flag is on", () => {
localStorage.setItem(FEATURES.VELSERA_EXPORT, "true");
mockUseRequestFileLocation.mockReturnValue(
buildFileLocationResult(undefined)
);
renderExportToPlatform(PROPS);
expect(getRequestLinkButton().hasAttribute("disabled")).toBe(false);
});

it("links to the import URL once the PFB location is available", () => {
localStorage.setItem(FEATURES.VELSERA_EXPORT, "true");
mockUseRequestFileLocation.mockReturnValue(
buildFileLocationResult(FILE_LOCATION)
);
renderExportToPlatform(PROPS);
const link = screen.getByRole("link", { name: BUTTON_LABEL });
expect(link.getAttribute("href")).toBe(IMPORT_URL);
expect(link.getAttribute("target")).toBe("_blank");
expect(link.getAttribute("rel")).toBe("noopener noreferrer");
});

it("keeps the form when the flag is off, even with a PFB location", () => {
mockUseRequestFileLocation.mockReturnValue(
buildFileLocationResult(FILE_LOCATION)
);
renderExportToPlatform(PROPS);
expect(screen.queryByRole("link", { name: BUTTON_LABEL })).toBeNull();
expect(getRequestLinkButton().hasAttribute("disabled")).toBe(true);
});
});
58 changes: 58 additions & 0 deletions __tests__/components/velseraExportMethod.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { createAppTheme } from "@databiosphere/findable-ui/lib/theme/theme";
import { ThemeProvider } from "@mui/material";
import { render, screen } from "@testing-library/react";
import type { ComponentProps } from "react";
import { VelseraExportMethod } from "../../app/components/Export/components/AnVILExplorer/platform/VelseraExportMethod/velseraExportMethod";
import { FEATURES } from "../../app/shared/entities";

jest.mock("@databiosphere/findable-ui/lib/hooks/useDownloadStatus", () => ({
useDownloadStatus: (): { disabled: boolean; message: string } => ({
disabled: false,
message: "",
}),
}));

const COMING_SOON = "Coming Soon";

const THEME = createAppTheme();

const PROPS = {
description: "BDC-SB is a cloud workspace.",
route: "/export/biodata-catalyst",
title: "Export to BioData Catalyst",
};

/**
* Renders VelseraExportMethod within the app theme.
* @param props - Export method props.
*/
function renderMethod(props: ComponentProps<typeof VelseraExportMethod>): void {
render(
<ThemeProvider theme={THEME}>
<VelseraExportMethod {...props} />
</ThemeProvider>
);
}

describe("VelseraExportMethod", () => {
afterEach(() => {
localStorage.clear();
});

it("shows coming soon when the Velsera export flag is off", () => {
renderMethod(PROPS);
expect(screen.getByText(COMING_SOON)).toBeTruthy();
});

it("enables the method when the Velsera export flag is on", () => {
localStorage.setItem(FEATURES.VELSERA_EXPORT, "true");
renderMethod(PROPS);
expect(screen.queryByText(COMING_SOON)).toBeNull();
});

it("shows coming soon when configured so, even with the flag on", () => {
localStorage.setItem(FEATURES.VELSERA_EXPORT, "true");
renderMethod({ ...PROPS, comingSoon: true });
expect(screen.getByText(COMING_SOON)).toBeTruthy();
});
});
40 changes: 28 additions & 12 deletions __tests__/site-config/export-constants.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,24 +54,24 @@ describe("EXPORTS constants", () => {
});

it.each(Object.entries(EXPORTS))(
"%s has all required keys",
"%s has non-empty string values for all required keys",
(_, config) => {
for (const key of REQUIRED_EXPORT_KEYS) {
expect(config).toHaveProperty(key);
expect(config[key]).toBeTruthy();
expect(typeof config[key]).toBe("string");
expect(config[key].trim().length).toBeGreaterThan(0);
}
}
);
});

it.each(Object.entries(EXPORTS))(
"%s has all non-empty string values",
(_, config) => {
for (const [, value] of Object.entries(config)) {
expect(typeof value).toBe("string");
expect(value.trim().length).toBeGreaterThan(0);
}
}
);
describe("platform import config", () => {
it.each(
Object.entries(EXPORTS).filter(([, config]) => config.platformImport)
)("%s has a valid import base URL and source", (_, config) => {
const { baseUrl, source } = config.platformImport!;
expect(new URL(baseUrl).protocol).toBe("https:");
expect(source.trim().length).toBeGreaterThan(0);
});
});

describe("BioData Catalyst export config", () => {
Expand All @@ -96,6 +96,13 @@ describe("EXPORTS constants", () => {
it("description mentions BDC-SB", () => {
expect(EXPORTS.BIO_DATA_CATALYST.description).toContain("BDC-SB");
});

it("has the BDC-SB PFB import endpoint and source", () => {
expect(EXPORTS.BIO_DATA_CATALYST.platformImport).toEqual({
baseUrl: "https://platform.sb.biodatacatalyst.nhlbi.nih.gov/import/pfb",
source: "bdc",
});
});
});

describe("CAVATICA export config", () => {
Expand Down Expand Up @@ -153,6 +160,15 @@ describe("EXPORT_METHODS constants", () => {
expect(EXPORT_METHODS.CANCER_GENOMICS_CLOUD).toBeDefined();
});

it("marks CAVATICA and Cancer Genomics Cloud as coming soon", () => {
expect(EXPORT_METHODS.CAVATICA.comingSoon).toBe(true);
expect(EXPORT_METHODS.CANCER_GENOMICS_CLOUD.comingSoon).toBe(true);
});

it("leaves BioData Catalyst coming soon to the Velsera export feature flag", () => {
expect(EXPORT_METHODS.BIO_DATA_CATALYST.comingSoon).toBeUndefined();
});

it.each(Object.entries(EXPORT_METHODS))(
"%s has all required keys",
(_, config) => {
Expand Down
Loading
Loading