Skip to content

test: unit tests for task queue (priority, retry, dead letter) - #452

Open
Kaycee276 wants to merge 6 commits into
Bitcoindefi:mainfrom
Kaycee276:fix/186-task-queue-tests
Open

test: unit tests for task queue (priority, retry, dead letter)#452
Kaycee276 wants to merge 6 commits into
Bitcoindefi:mainfrom
Kaycee276:fix/186-task-queue-tests

Conversation

@Kaycee276

Copy link
Copy Markdown
Contributor

Title

test: unit tests for task queue (priority, retry, dead letter)

Context

PR #178 introduced a task queue with priority ordering, a dead-letter queue, and retry logic with backoff, but it did not include tests or fully expose the dequeue(), peekNext(), and retryAll() methods. This PR fulfills the acceptance criteria for issue #186 by implementing the missing methods and writing comprehensive unit tests.

Implementation Details

  • Added peekNext() to lib/agent-runtime/task-queue.ts: Retrieves the highest-priority pending task without modifying its state.
  • Added dequeue() to lib/agent-runtime/task-queue.ts: Uses peekNext() to get the next task and transitions its status to "leased".
  • Added retryAll() to lib/agent-runtime/task-queue.ts: Iterates over the dead-letter queue and calls retryDeadLetterTask() on all failed tasks.
  • Added Test Suite at lib/task-queue/__tests__/task-queue.test.ts:
    • Enqueues multiple priority levels to assert descending priority dequeue.
    • Tests equal priorities to ensure FIFO ordering.
    • Uses vi.useFakeTimers() to fast-forward through the N retry attempts and verify the task eventually moves to the dead-letter queue.
    • Verifies retryAll() accurately resets dead-letter tasks back to pending.
    • Asserts that dequeue() on an empty queue safely returns null.
    • Mocks Request objects to test both the POST /api/tasks and GET /api/tasks/[id] Next.js route handlers directly.

Verification

  • Unit tests verify all 8 required behaviors using Vitest.
  • Re-ran the existing suite to ensure no regressions were introduced to enqueueTask, failTask, or API routes.
  • Proof: (Please remember to attach your demo video or screenshots here as per the mandatory delivery requirements before opening the PR)

closes #186

@leocagli

Copy link
Copy Markdown
Collaborator

Hi @Kaycee276 👋 — the tests themselves look good (+158, clean diff, Sonar green), but the required CI fails at npm ci with ERESOLVE: your branch's lockfile resolves typescript@7.0.2, which conflicts with @typescript-eslint/eslint-plugin@8.64.x (requires TS <7). main is on typescript@5.7.3 and its CI is green.

Fix: rebase your branch onto the latest main (make sure your fork's main is synced first) and don't carry over package.json/package-lock.json changes — your PR shouldn't need any. Once CI is green I'll merge right away. 🙏

@Kaycee276

Copy link
Copy Markdown
Contributor Author

Hi @leocagli
Thanks for the review! 🙏

I've pushed an update that addresses your feedback:

  • Reverted the package.json and lockfile changes so they are now perfectly in sync with main (no more ESLint vs TypeScript 7.0 ERESOLVE conflicts).
  • Fixed the TS1005 syntax error in lib/agent-runtime/task-queue.ts that occurred during my previous conflict resolution.

The CI should be completely green now. Let me know if anything else is needed!

@leocagli

Copy link
Copy Markdown
Collaborator

Hi @Kaycee276 👋 — quick update: video/screenshots are not required to merge anymore, just a bonus — if you add one it'll be considered for GrantFox rewards on this issue. So I'll review this on code + checks alone now. 🙏

@leocagli

Copy link
Copy Markdown
Collaborator

Needs a rebase — and that is our fault, not yours

We just merged a batch of PRs that had been waiting far too long for review. Yours was in that batch and was green, but the merges ahead of it touched overlapping files, so this branch now conflicts with main.

To get it in:

git fetch origin
git rebase origin/main
# resolve conflicts, then
git push --force-with-lease

Once CI goes green again, comment here and it gets merged — no second review round needed for the parts that were already approved.

Sorry for the churn. The delay that caused this was on our side: this PR sat for weeks while other work landed on top of it.

@sonarqubecloud

Copy link
Copy Markdown

Comment thread package.json
Comment on lines 4 to 16
@@ -17,9 +14,6 @@
"secretlint": "secretlint \"**/*\"",
"size-limit": "size-limit",
"test": "vitest run",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug: package.json reverts recent main deps & removes Playwright E2E

This test-only PR's package.json changes revert changes recently merged to main (introduced by #444/#430): it deletes the engines (node >=22) field, removes the test:e2e/test:e2e:headed/test:e2e:ui scripts and the @playwright/test devDependency, and downgrades framer-motion (^12.42.2→^11.15.0), eslint-config-next (^16.2.10→^16.2.0), and webpack (^5.108.4→^5.97.1). These look like a botched merge-conflict resolution (commit 9fed45e) rather than intentional edits. Merging this would silently roll back the E2E test infrastructure and pin older dependencies. Restore the target-branch versions of these package.json entries and keep only the additions relevant to this PR.

Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown
CI failed: CI failures caused by a package-lock sync mismatch during `npm ci` and a permission denied error (`EACCES`) when writing task queue files to the root directory `/.data` during unit tests.

Overview

Two distinct change-related failures occurred across the CI jobs: an npm ci dependency sync failure due to a lock file mismatch, and a unit test failure caused by attempting to write dead-letter queue data to the absolute root path /.data without sufficient permissions.

Failures

Package-Lock Sync Mismatch (confidence: high)

  • Type: dependency
  • Affected jobs: 97568697837
  • Related to change: yes
  • Root cause: npm ci requires package.json and package-lock.json to be completely in sync, but a version mismatch was detected (e.g., framer-motion).
  • Suggested fix: Run npm install locally to update package-lock.json so that it matches package.json, then commit and push the updated lock file.

Task Queue Permission Denied Error (confidence: high)

  • Type: test
  • Affected jobs: 97568697739
  • Related to change: yes
  • Root cause: The task queue persistence logic attempts to create and write to a directory at the absolute path /.data (via mkdirSync / persistDeadLetterQueue), which fails with EACCES: permission denied because the test runner lacks root privileges.
  • Suggested fix: Update lib/agent-runtime/task-queue.ts to use a relative path or a configurable directory (such as within os.tmpdir() or process.cwd()) for persisting the dead-letter queue during tests and runtime, rather than hardcoding the root path /.data.

Summary

  • Change-related failures: 2 failures (one dependency lock file out-of-sync error, one task queue file permission error in tests)
  • Infrastructure/flaky failures: 0
  • Recommended action: Regenerate and commit the updated package-lock.json via npm install, and update lib/agent-runtime/task-queue.ts to use a writable temporary or relative directory instead of /.data.
Code Review ⚠️ Changes requested 0 resolved / 2 findings

Adds unit tests and missing methods for the task queue, but the changes incorrectly revert recent dependencies and remove Playwright E2E coverage in package.json, and write dead-letter queue data to the real filesystem path.

⚠️ Bug: package.json reverts recent main deps & removes Playwright E2E

📄 package.json:4-16 📄 package.json:66 📄 package.json:91 📄 package.json:104 📄 package.json:113

This test-only PR's package.json changes revert changes recently merged to main (introduced by #444/#430): it deletes the engines (node >=22) field, removes the test:e2e/test:e2e:headed/test:e2e:ui scripts and the @playwright/test devDependency, and downgrades framer-motion (^12.42.2→^11.15.0), eslint-config-next (^16.2.10→^16.2.0), and webpack (^5.108.4→^5.97.1). These look like a botched merge-conflict resolution (commit 9fed45e) rather than intentional edits. Merging this would silently roll back the E2E test infrastructure and pin older dependencies. Restore the target-branch versions of these package.json entries and keep only the additions relevant to this PR.

⚠️ Bug: New task-queue test writes DLQ to real /.data path

📄 lib/task-queue/tests/task-queue.test.ts:9-16 📄 lib/task-queue/tests/task-queue.test.ts:42-56 📄 lib/agent-runtime/task-queue.ts:85-87 📄 lib/agent-runtime/task-queue.ts:145-154

The new test at lib/task-queue/tests/task-queue.test.ts dead-letters tasks (the retry/dead-letter and retryAll cases) but never sets process.env.TASK_DLQ_FILE, so persistDeadLetterQueue() falls back to the production default /.data/task-dlq.json and calls mkdirSync('/.data') + writeFileSync. On a non-root CI runner this throws EACCES and fails the test; elsewhere it creates stray files and can pollute state across runs. The existing suite (tests/api/tasks.test.ts) sets TASK_DLQ_FILE to a temp dir in beforeEach — do the same here (e.g. point it at an os.tmpdir() path in beforeEach and restore/clean up in afterEach).

🤖 Prompt for agents
Code Review: Adds unit tests and missing methods for the task queue, but the changes incorrectly revert recent dependencies and remove Playwright E2E coverage in package.json, and write dead-letter queue data to the real filesystem path.

1. ⚠️ Bug: package.json reverts recent main deps & removes Playwright E2E
   Files: package.json:4-16, package.json:66, package.json:91, package.json:104, package.json:113

   This test-only PR's package.json changes revert changes recently merged to main (introduced by #444/#430): it deletes the `engines` (node >=22) field, removes the `test:e2e`/`test:e2e:headed`/`test:e2e:ui` scripts and the `@playwright/test` devDependency, and downgrades `framer-motion` (^12.42.2→^11.15.0), `eslint-config-next` (^16.2.10→^16.2.0), and `webpack` (^5.108.4→^5.97.1). These look like a botched merge-conflict resolution (commit 9fed45e) rather than intentional edits. Merging this would silently roll back the E2E test infrastructure and pin older dependencies. Restore the target-branch versions of these package.json entries and keep only the additions relevant to this PR.

2. ⚠️ Bug: New task-queue test writes DLQ to real /.data path
   Files: lib/task-queue/__tests__/task-queue.test.ts:9-16, lib/task-queue/__tests__/task-queue.test.ts:42-56, lib/agent-runtime/task-queue.ts:85-87, lib/agent-runtime/task-queue.ts:145-154

   The new test at lib/task-queue/__tests__/task-queue.test.ts dead-letters tasks (the retry/dead-letter and retryAll cases) but never sets `process.env.TASK_DLQ_FILE`, so `persistDeadLetterQueue()` falls back to the production default `/.data/task-dlq.json` and calls `mkdirSync('/.data')` + `writeFileSync`. On a non-root CI runner this throws EACCES and fails the test; elsewhere it creates stray files and can pollute state across runs. The existing suite (__tests__/api/tasks.test.ts) sets TASK_DLQ_FILE to a temp dir in beforeEach — do the same here (e.g. point it at an os.tmpdir() path in beforeEach and restore/clean up in afterEach).

Tip

Comment Gitar fix CI or enable auto-apply: gitar auto-apply:on

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Comment on lines +9 to +16
beforeEach(() => {
resetTaskQueueForTests()
jest.useFakeTimers()
})

afterEach(() => {
jest.useRealTimers()
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug: New task-queue test writes DLQ to real /.data path

The new test at lib/task-queue/tests/task-queue.test.ts dead-letters tasks (the retry/dead-letter and retryAll cases) but never sets process.env.TASK_DLQ_FILE, so persistDeadLetterQueue() falls back to the production default /.data/task-dlq.json and calls mkdirSync('/.data') + writeFileSync. On a non-root CI runner this throws EACCES and fails the test; elsewhere it creates stray files and can pollute state across runs. The existing suite (tests/api/tasks.test.ts) sets TASK_DLQ_FILE to a temp dir in beforeEach — do the same here (e.g. point it at an os.tmpdir() path in beforeEach and restore/clean up in afterEach).

Was this helpful? React with 👍 / 👎

@leocagli

Copy link
Copy Markdown
Collaborator

Hola @Kaycee276. No mergeo este PR todavía porque rompe el build, y te paso exactamente qué es y dónde, así lo cerrás rápido.

El fallo, en Typecheck, tests, build, and guards:

FAIL  lib/task-queue/__tests__/task-queue.test.ts > Task Queue > A task that fails N times moves to the dead-letter queue
Error: EACCES: permission denied, mkdir '/.data'
FAIL  lib/task-queue/__tests__/task-queue.test.ts > Task Queue > retryAll() moves dead-letter tasks back to the main queue
Error: EACCES: permission denied, mkdir '/.data'

La causa está en lib/agent-runtime/task-queue.ts:86:

return process.env.TASK_DLQ_FILE ?? "/.data/task-dlq.json";

Esa ruta por defecto es absoluta a la raíz del disco, no relativa al proyecto. Después, en la línea 150:

mkdirSync(dirname(dlqFilePath), { recursive: true });

intenta crear literalmente /.data, y el runner de CI no escribe en /. Que funcione en tu máquina es esperable si corrés como root o con permisos sobre la raíz; el runner no los tiene, y tampoco debería.

El resto del repo ya resuelve esto de una forma, y conviene seguirla para no tener dos criterios:

// lib/webhooks/store.ts:24
const DEFAULT_WEBHOOKS_PATH = join(process.cwd(), ".data", "webhooks.json")
// lib/error-log.ts:22
const DEFAULT_LOG_PATH = join(cwd(), ".data", "error-log.json");
// lib/agents/agent-position-store.ts:75
const DEFAULT_POSITIONS_DIR = join(process.cwd(), ".data", "positions")

O sea que el arreglo es agregar el process.cwd() que falta:

return process.env.TASK_DLQ_FILE ?? join(process.cwd(), ".data", "task-dlq.json");

Y una segunda cosa que te recomiendo aunque el arreglo de arriba alcance para que pase el CI. El test no setea TASK_DLQ_FILE, así que escribe en el .data real del proyecto y deja estado entre corridas. Vale la pena apuntarlo a un temporal en el beforeEach y limpiarlo en el afterEach:

beforeEach(() => {
  process.env.TASK_DLQ_FILE = join(mkdtempSync(join(tmpdir(), "dlq-")), "task-dlq.json")
})

Así los dos tests que fallan quedan aislados de verdad, y no dependen de en qué directorio se los corra ni de lo que haya quedado de la corrida anterior. Para tests de cola con dead-letter eso importa, porque el segundo test (retryAll()) lee lo que dejó el primero.

El resto del PR está bien: Passport spec guard y SonarCloud Code Analysis los pasa. Es sólo esto.

Aviso aparte que no tiene que ver con vos: el check SonarCloud Analysis está en rojo en todos los PRs del repo desde el 17 de agosto, por una configuración de SonarCloud, no por el código. Está documentado en la #491, así que ignoralo.

Soy Leo Cagliero, del programa Starmaker, trabajando en Cosmos LATAM y Open Stellar.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tests: unit tests for task queue (priority, retry, dead letter)

2 participants