Skip to content
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

All notable changes to this project will be documented in this file.

## [1.56.4] (03/07/2026)
Cap the liquid-test polling interval at 5 seconds. The delay between result polls grew 5% per poll without a limit, so long test runs (~10 minutes) were only checked every 30-60 seconds and a finished run could sit unnoticed for up to a minute.

## [1.56.3] (03/07/2026)
Improve `run-test --status` output for CI: surface the underlying error message when a run ends in `test_error`/`internal_error` (previously reported as a bare `FAILED` with no reason), and suppress the progress spinner when stdout is not a TTY (it flooded CI logs with hundreds of "Running tests.." frames).

Expand Down
6 changes: 5 additions & 1 deletion lib/liquidTestRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,10 @@ function buildTestParams(firmId, templateType, handle, testName = "", renderMode
async function fetchResult(firmId, testRunId, templateType) {
let testRun = { status: "started" };
let pollingDelay = 1000;
// Without a cap the 5% growth leaves long test runs polling every 30-60s (or more),
// so a finished run can sit unnoticed for up to a minute. Capping keeps the request
// rate modest (at most one poll per 5s per run) while bounding the detection lag.
const maxPollingDelay = 5000;
const waitingLimit = 2000000;

spinner.spin("Running tests..");
Expand All @@ -322,7 +326,7 @@ async function fetchResult(firmId, testRunId, templateType) {
const response = await SF.readTestRun(firmId, testRunId, templateType);
testRun = response.data;
waitingTime += pollingDelay;
pollingDelay *= 1.05;
pollingDelay = Math.min(pollingDelay * 1.05, maxPollingDelay);
if (waitingTime >= waitingLimit) {
spinner.stop();
consola.error("Timeout. Try to run your test again");
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "silverfin-cli",
"version": "1.56.3",
"version": "1.56.4",
"description": "Command line tool for Silverfin template development",
"main": "index.js",
"license": "MIT",
Expand Down
1 change: 1 addition & 0 deletions tests/TESTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,7 @@ Source: `lib/liquidTestRunner.js`
| `runTests` | should return undefined when config is missing | Verifies that `undefined` is returned and an error is logged when the template's `config.json` does not exist. |
| `runTests` | should return undefined when YAML test file does not exist | Verifies that `undefined` is returned and an error is logged when the YAML test file path in the config does not exist on disk. |
| `runTests` | should run tests and return testRun result when YAML exists and API responds | Verifies that `createTestRun` and `readTestRun` are called and the completed test-run object is returned. |
| `runTests` | should cap the polling interval at 5 seconds on long test runs | Verifies that the poll delay growth is capped at 5s, so a long-running test is still detected promptly once it finishes. |
| `runTests` | should log info and return false when YAML file is empty (single line) | Verifies that `undefined` is returned and an info message about no stored tests is logged when the YAML file contains only a comment. |
| `runTests` | should run tests for accountTemplate type | Verifies that `runTests` works end-to-end for the `accountTemplate` type and returns a completed test-run object. |
| `runTestsWithOutput` | should exit with error for invalid templateType | Verifies that an invalid `templateType` causes an error to be logged and `process.exit(1)` to be called. |
Expand Down
35 changes: 35 additions & 0 deletions tests/lib/liquidTestRunner.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,41 @@ describe("runTests", () => {
expect(result.testRun.tests).toEqual(makePassedTests());
});

it("should cap the polling interval at 5 seconds on long test runs", async () => {
const handle = "reconciliation_text_1";
setupFsUtilsMocks(handle);

const testDir = path.join(tempDir, "reconciliation_texts", handle, "tests");
fs.mkdirSync(testDir, { recursive: true });
fs.writeFileSync(path.join(testDir, `${handle}_liquid_test.yml`), SIMPLE_YAML);

ReconciliationText.read.mockReturnValue({
handle,
text: "{% assign x = 1 %}",
text_parts: [],
});

SF.createTestRun = jest.fn().mockResolvedValue({ data: 42 });
// Keep the run "started" for 60 polls - enough for the uncapped 5% growth to pass
// 5s (1000 * 1.05^33 ≈ 5000) - then complete.
let polls = 0;
SF.readTestRun = jest.fn().mockImplementation(async () => {
polls += 1;
return { data: makeTestRun(polls < 60 ? "started" : "completed", makePassedTests()) };
});
const setTimeoutSpy = jest.spyOn(global, "setTimeout");

const promise = runTests(1001, "reconciliationText", handle, "", false, "none");
await jest.runAllTimersAsync();
const result = await promise;

expect(result.testRun.status).toBe("completed");
const delays = setTimeoutSpy.mock.calls.map((call) => call[1]).filter((delay) => typeof delay === "number");
// The cap must actually be reached, and never exceeded.
expect(Math.max(...delays)).toBe(5000);
setTimeoutSpy.mockRestore();
});

it("should log info and return undefined when YAML file is empty (single line)", async () => {
const handle = "reconciliation_text_1";
setupFsUtilsMocks(handle);
Expand Down