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
7 changes: 7 additions & 0 deletions src/classes/tapis/adapters/TapisJobService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ export class TapisJobService {
): Jobs.JobFileInput[] {
const jobInputs =
app.jobAttributes?.fileInputs?.flatMap((fileInput) => {
if (fileInput.inputMode === Apps.FileInputModeEnum.Fixed) {
// FIXED inputs are locked by the app definition; Tapis injects
// them from the app at submission. No model component input or
// user-provided dataset is required.
return [];
}

const modelInput = model.input_files.find((input) => input.name === fileInput.name);

if (!modelInput) {
Expand Down
42 changes: 42 additions & 0 deletions src/classes/tapis/adapters/tests/fixtures/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,45 @@ export const appWithUnknownRequiredInput: Apps.TapisApp = {
]
}
} as Apps.TapisApp;

export const appWithFixedInput: Apps.TapisApp = {
...baseApp,
jobAttributes: {
...baseApp.jobAttributes,
fileInputs: [
{
name: "Fixed Input",
description: "Locked by app, user cannot override",
inputMode: "FIXED",
autoMountLocal: true,
sourceUrl: "tapis://ls6/home/${apiUserId}/fixed-input.txt",
targetPath: "fixed.txt"
}
]
}
} as Apps.TapisApp;

export const appWithMixedInputs: Apps.TapisApp = {
...baseApp,
jobAttributes: {
...baseApp.jobAttributes,
fileInputs: [
{
name: "optional_file",
description: "An optional supplementary input file",
inputMode: "OPTIONAL",
autoMountLocal: true,
sourceUrl: null,
targetPath: "optional.dat"
},
{
name: "Fixed Input",
description: "Locked by app, user cannot override",
inputMode: "FIXED",
autoMountLocal: true,
sourceUrl: "tapis://ls6/home/${apiUserId}/fixed-input.txt",
targetPath: "fixed.txt"
}
]
}
} as Apps.TapisApp;
40 changes: 39 additions & 1 deletion src/classes/tapis/adapters/tests/jobs.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import seeds from "./fixtures/seeds";
import app, { appWithOptionalInput, appWithUnknownRequiredInput } from "./fixtures/app";
import app, {
appWithOptionalInput,
appWithUnknownRequiredInput,
appWithFixedInput,
appWithMixedInputs
} from "./fixtures/app";
import model, { modelWithOptionalInput } from "./fixtures/model";
import jobFileInputsExpected from "./expected/jobFileInputs";
import { expectedJobParameterSetNonDefault } from "./expected/jobParameterSet";
Expand Down Expand Up @@ -62,6 +67,39 @@ test("throws when app fileInput name is not found in model.input_files", () => {
).toThrow("Component input not found");
});

test("FIXED input is skipped — Tapis injects sourceUrl from app definition", () => {
const jobService = new TapisJobService(
new Jobs.JobsApi(),
new Jobs.SubscriptionsApi(),
new Jobs.ShareApi()
);
const seedNoDatasets = { ...seeds[0], datasets: {} };
expect(() =>
jobService.createJobFileInputsFromSeed(seedNoDatasets, appWithFixedInput, model)
).not.toThrow();
const jobInputs = jobService.createJobFileInputsFromSeed(
seedNoDatasets,
appWithFixedInput,
model
);
expect(jobInputs.find((i) => i.name === "Fixed Input")).toBeUndefined();
expect(jobInputs).toHaveLength(0);
});

test("FIXED input alongside OPTIONAL input — both safely omitted when unbound", () => {
const jobService = new TapisJobService(
new Jobs.JobsApi(),
new Jobs.SubscriptionsApi(),
new Jobs.ShareApi()
);
const jobInputs = jobService.createJobFileInputsFromSeed(
seedWithMissingOptionalInput,
appWithMixedInputs,
modelWithOptionalInput
);
expect(jobInputs).toHaveLength(0);
});

test("optional input with datasets present is included", () => {
const jobService = new TapisJobService(
new Jobs.JobsApi(),
Expand Down
Loading