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 <[email protected]>
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");
+  });
+});

Reply via email to