Skip to content

Issue 390 - #391

Merged
allanbowe merged 3 commits into
mainfrom
issue-390
Jul 14, 2026
Merged

Issue 390#391
allanbowe merged 3 commits into
mainfrom
issue-390

Conversation

@YuryShkoda

Copy link
Copy Markdown
Member

Issue

Closes #390

Intent

Two small, independent fixes bundled on this branch:

  1. Stop ExecutionController from silently discarding a failed JS/PY/R
    session's state.
  2. Stop drive.spec.ts from leaking real files into the shared
    sasjs_root directory, which was causing spurious failures on
    subsequent local test runs.

Implementation

1. JS/PY/R session state overwrite

For the JS/PY/R runtimes, processProgram sets session.state = failed
(with session.failureReason) itself when the spawned interpreter process
exits non-zero — without throwing:

// processProgram.ts
.catch((err) => {
  session.state = SessionState.failed
  session.failureReason = err.toString()
  ...
})

Immediately after processProgram returns, Execution.ts unconditionally
overwrote that state:

// Execution.ts (before)
session.state = SessionState.completed

So a crashed JS/PY/R session had its failed state silently stomped back
to completed before ever being inspected or cleaned up. The client-visible
HTTP error response was unaffected (that's built from the log/webout files,
not session.state), but anything downstream inspecting the session object
itself — e.g. scheduleSessionDestroy's expiresAfterMins branch in
Session.ts — would see a crashed session mis-reported as successful.

Fix (Execution.ts:130-132): guard the assignment so a failed state
is never overwritten. No effect on SAS, which manages its own state via a
separate spawned-process lifecycle.

Tests (Execution.spec.ts, new): covers both the failure case (state
stays failed) and the success case (state still becomes completed), so
neither direction regresses.

2. drive.spec.ts leaking into the real sasjs_root

drive.spec.ts already isolates itself into a unique, timestamped
tmpFolder for getSasjsRootFolder()/getUploadsFolder(), cleaned up via
afterAll(() => deleteFolder(tmpFolder)). But getFilesFolder() — what
nearly every test in this file actually writes to — resolves through a
separate, unmocked function (getSasjsDriveFolder()process.driveLoc),
so every run left real files/folders (level1, my/path/...) behind in
the shared api/sasjs_root/drive/files. On a subsequent run this caused:
folder-listing tests seeing stale entries left by a prior run, and
file-creation tests getting 409 Conflict against files a previous run had
already created.

Fix: mock getFilesFolder() the same way getUploadsFolder() already
is, so it resolves inside the same isolated tmpFolder and gets cleaned up
by the existing afterAll.

Verification: ran the suite twice in a row from a clean slate — both
runs pass, and the real sasjs_root/drive directory is never touched by
either run.

Checks

  • Code is formatted correctly (npm run lint:fix).
  • Any new functionality has been unit tested.
  • All unit tests are passing (npm test).
  • All CI checks are green.
  • Reviewer is assigned.

For JS/PY/R, processProgram sets session.state = failed (with
failureReason) itself when the interpreter process exits non-zero,
without throwing. ExecutionController.executeProgram then
unconditionally set session.state = completed right after
processProgram returned, silently overwriting that - so anything
downstream inspecting session.state (e.g. scheduleSessionDestroy's
expiresAfterMins branch) would see a crashed session mis-reported as
successful.

Guard the assignment so a failed state is never overwritten. No
effect on SAS, which sets state independently via its own spawned
process lifecycle.

Added Execution.spec.ts covering both the failure case (state stays
failed) and the success case (state still becomes completed), to
guard against regressing in either direction.
drive.spec.ts already isolates itself into a unique, timestamped
tmpFolder for getSasjsRootFolder()/getUploadsFolder(), cleaned up via
afterAll(() => deleteFolder(tmpFolder)). But getFilesFolder() - what
nearly every test in this file actually writes to - resolves through
a separate, unmocked function (getSasjsDriveFolder()/process.driveLoc),
so every run left real files/folders (e.g. 'level1', 'my/path/...')
behind in the shared api/sasjs_root/drive/files, causing later runs
to fail: folder-listing tests saw stale entries, and file-creation
tests got 409 Conflict against files a previous run already created.

Mock getFilesFolder() the same way getUploadsFolder() already is, so
it resolves inside the same isolated tmpFolder and gets cleaned up
by the existing afterAll. Verified by running the suite twice in a
row from a clean slate: both runs pass, and the real sasjs_root/drive
is never touched.
@YuryShkoda YuryShkoda self-assigned this Jul 14, 2026
# Conflicts:
#	api/src/controllers/internal/spec/Execution.spec.ts
@allanbowe
allanbowe merged commit 40b9fa8 into main Jul 14, 2026
3 checks passed
@allanbowe
allanbowe deleted the issue-390 branch July 14, 2026 09:02
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 0.39.6 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Execution.ts overwrites a failed JS/PY/R session's state back to completed

2 participants