You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ya...@apache.org on 2026/04/25 05:16:45 UTC
(airavata-portals) 28/34: test(launcher): full happy-path integration spec (mocked API)
This is an automated email from the ASF dual-hosted git repository.
yasithdev pushed a commit to branch feat/generic-experiment-launcher
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git
commit ff10e84e0bf8e381973f03474fe8d21b91aad406
Author: yasithdev <ya...@icloud.com>
AuthorDate: Fri Apr 24 23:12:09 2026 -0400
test(launcher): full happy-path integration spec (mocked API)
---
.../tests/unit/integration/launch-flow.spec.ts | 110 +++++++++++++++++++++
1 file changed, 110 insertions(+)
diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/integration/launch-flow.spec.ts b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/integration/launch-flow.spec.ts
new file mode 100644
index 000000000..e0fc5b4ac
--- /dev/null
+++ b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/integration/launch-flow.spec.ts
@@ -0,0 +1,110 @@
+import { mount, flushPromises } from "@vue/test-utils";
+import { createPinia, setActivePinia } from "pinia";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import LaunchContainer from "../../../js/containers/LaunchContainer.vue";
+
+const APP = {
+ app_id: "namd", name: "NAMD", category: "MD",
+ content: { kind: "github" as const, url: "g" },
+ interfaces: [{
+ name: "run",
+ inputs: [
+ { name: "sim_dir", type: "dir" as const, required: true },
+ { name: "steps", type: "int" as const, required: true },
+ ],
+ outputs: [{ name: "trajectory", type: "file" as const }],
+ }],
+};
+
+const PROFILE = {
+ project_id: "p1", allocation_id: "NSF-1",
+ compute_resources: [{
+ compute_resource_id: "bridges-2", name: "Bridges-2",
+ mapped_storage: { storage_id: "scratch", scratch_path: "/scratch/p1" },
+ partitions: [{ name: "RM", max_walltime: "48:00:00", max_nodes: 64, cpus_per_node: 128 }],
+ }],
+};
+
+vi.mock("django-airavata-common-ui/js/services/launcherService", () => ({
+ launcherService: {
+ listApplications: vi.fn(),
+ listUserStorages: vi.fn(),
+ listProjects: vi.fn(),
+ getProjectResourceProfile: vi.fn(),
+ generatePreview: vi.fn(),
+ launchExperiment: vi.fn(),
+ },
+}));
+
+import { launcherService } from "django-airavata-common-ui/js/services/launcherService";
+
+describe("launch flow integration", () => {
+ beforeEach(() => {
+ setActivePinia(createPinia());
+ localStorage.clear();
+ vi.mocked(launcherService.listApplications).mockResolvedValue({ results: [APP] });
+ vi.mocked(launcherService.listUserStorages).mockResolvedValue({
+ results: [{ storage_id: "my-home", name: "Home", is_primary: true }],
+ });
+ vi.mocked(launcherService.listProjects).mockResolvedValue({
+ results: [{ project_id: "p1", name: "lab" }],
+ });
+ vi.mocked(launcherService.getProjectResourceProfile).mockResolvedValue(PROFILE);
+ vi.mocked(launcherService.generatePreview).mockResolvedValue({
+ invocation_command: "sbatch /tmp/run.sh",
+ script_contents: "#!/bin/bash\necho hi",
+ warnings: [],
+ });
+ vi.mocked(launcherService.launchExperiment).mockResolvedValue({ experiment_id: "exp-7" });
+ Object.defineProperty(window, "location", {
+ value: { href: "http://localhost/", assign: vi.fn() }, writable: true,
+ });
+ vi.spyOn(window.history, "replaceState").mockImplementation(() => {});
+ });
+
+ it("walks tab 1 → tab 2 → tab 3 → launch", async () => {
+ const w = mount(LaunchContainer);
+ await flushPromises();
+
+ // Meta
+ await w.find("input[data-test='exp-name']").setValue("test-run");
+ await w.find("select[data-test='exp-project']").setValue("p1");
+
+ // App + interface
+ await w.find("[data-test='app-tile-namd']").trigger("click");
+ await flushPromises();
+ await w.find("[data-test='iface-card-run']").trigger("click");
+
+ // Inputs + output
+ // sim_dir is a dir input: its storage select is the sibling select immediately
+ // before the file-path input (inside the same flex row div).
+ const simDirInput = w.find("input[data-test='file-path-sim_dir']");
+ // The FileInputRow renders: select (storage) | input (path) — find all selects
+ // and pick the one whose next input sibling has data-test='file-path-sim_dir'
+ const simDirStorageSelect = w.findAll("select").find((s) => {
+ const next = s.element.nextElementSibling;
+ return next instanceof HTMLInputElement && next.dataset.test === "file-path-sim_dir";
+ });
+ if (simDirStorageSelect) await simDirStorageSelect.setValue("my-home");
+ await simDirInput.setValue("/x");
+ await w.find("input[data-test='scalar-steps']").setValue("100");
+ // Output: find the storage select within the trajectory output row
+ const outSelect = w.findAll("select").find((s) => s.element.parentElement?.outerHTML.includes("trajectory"));
+ if (outSelect) await outSelect.setValue("my-home");
+ await w.find("input[data-test='file-out-path-trajectory']").setValue("/y");
+
+ // Tab 2
+ await w.findAll("button[role='tab']")[1].trigger("click");
+ await flushPromises();
+ await w.find("select[data-test='cr']").setValue("bridges-2");
+ await w.find("select[data-test='partition']").setValue("RM");
+
+ // Tab 3
+ await w.findAll("button[role='tab']")[2].trigger("click");
+ await flushPromises();
+ expect(w.text()).toContain("sbatch /tmp/run.sh");
+ await w.find("button[data-test='launch']").trigger("click");
+ await flushPromises();
+ expect(window.location.href).toBe("/workspace/experiments/exp-7");
+ });
+});