Skip to content

Feat/compt 78 test suite#6

Closed
saadmoumou wants to merge 4 commits intomasterfrom
feat/COMPT-78-test-suite
Closed

Feat/compt 78 test suite#6
saadmoumou wants to merge 4 commits intomasterfrom
feat/COMPT-78-test-suite

Conversation

@saadmoumou
Copy link
Copy Markdown
Contributor

Summary

  • What does this PR change?

Why

  • Why is this change needed?

Checklist

  • Added/updated tests (if behavior changed)
  • npm run lint passes
  • npm run typecheck passes
  • npm test passes
  • npm run build passes
  • Added a changeset (npx changeset) if this affects consumers

Notes

  • Anything reviewers should pay attention to?

Zaiidmo and others added 4 commits April 1, 2026 21:04
* feat(COMPT-75): define typed scheduled job contracts and IScheduler port

- Add ScheduledJob type: name, handler, and one of cron | interval | timeout
- Add IScheduler interface: schedule, unschedule, reschedule, list
- Add ScheduledJobStatus type: name, cron, lastRun, nextRun, isRunning
- Add DuplicateJobError thrown when registering a duplicate job name
- Enforce cron/interval/timeout mutual exclusivity via discriminated union
- Add CronExpression named constants for human-readable schedules
- Add cron fluent builder: dailyAt, weeklyOn, monthlyOn, every().minutes()
- Update tsconfig.build.json: CommonJS output, Task 1 files only
- Update package.json: name @ciscode/scheduler-kit, version 0.0.0

* test(COMPT-75): add unit tests for CronExpression and cron builder

* feat(COMPT-75): add ScheduledJobStatus type and status/listStatus methods

- Add ScheduledJobStatus type: name, cron, lastRun, nextRun, isRunning
- Add status(name) and listStatus() to IScheduler interface and SchedulerService
- Track lastRun timestamp after each execution
- Store cronJob reference for nextRun via cronJob.nextDate()
- Export ScheduledJobStatus from public API
- Fix lint: no-misused-promises, no-floating-promises, prettier formatting

* style(COMPT-75): format all files with prettier

---------

Co-authored-by: saad moumou <saad.moumou.coder@gmail.com>
* feat(COMPT-76): NestJS SchedulerModule and SchedulerService integration

- SchedulerModule.register() and registerAsync() dynamic module factories
- SchedulerService: schedule(), reschedule(), unschedule(), list(), status(), listStatus()
- @Cron, @interval, @timeout decorators with optional job name
- MetadataScanner auto-discovers decorated provider methods on onModuleInit
- DuplicateJobError guard against double registration
- IScheduler interface and ScheduledJobStatus type
- CronExpression constants and cron fluent builder
- Full unit test coverage across module, service and decorators

* test(COMPT-76): add unit tests for CronExpression and cron builder

---------

Co-authored-by: saad moumou <saad.moumou.coder@gmail.com>
…g behaviors

- jest.useFakeTimers() for all time-based tests — no real waiting
- @interval: fires every N ms, stops after unschedule, does not fire before interval
- @timeout: fires exactly once, removes itself from registry, does not fire early
- schedule(): job added and fires on tick
- unschedule(): timer cleared, handler not called after removal
- reschedule(): new schedule applied atomically, old timer cancelled
- Job error: caught via onJobError, scheduler continues — other jobs unaffected
- Concurrent guard: second execution skipped when first still running (isRunning lock)
- status() / listStatus(): reflect isRunning and lastRun correctly
- @Cron: registered and unscheduled without throwing
- Default onJobError: delegates to Logger.error
- Coverage: 98.63% statements, 98.11% branches, 95.34% functions (threshold 85%)
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 9, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
31.9% Coverage on New Code (required ≥ 80%)
4.9% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Introduces a new @ciscode/scheduler-kit NestJS module providing a scheduler service with cron/interval/timeout jobs, decorators for auto-registration, and a Jest test suite; updates build/lint/CI configuration to match the new package.

Changes:

  • Added SchedulerService + SchedulerModule with decorator-based job discovery and job status APIs.
  • Added cron helpers (CronExpression, fluent cron builder) and extensive unit tests.
  • Updated TypeScript build config, ESLint/lint-staged setup, Husky hook, and GitHub workflows for release/publish checks.

Reviewed changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tsconfig.eslint.json Expands ESLint TS project include patterns (adds *.mjs).
tsconfig.build.json Builds a curated set of source entrypoints and forces CJS output.
src/services/scheduler.service.ts New scheduler implementation (registry, scheduling, status, reschedule).
src/services/scheduler.service.spec.ts Unit tests for scheduling, concurrency guard, status, errors, cron behavior.
src/scheduler.module.ts New dynamic module that scans decorated provider methods and schedules jobs on init.
src/scheduler.module.spec.ts Unit tests for module auto-registration and dynamic module factory outputs.
src/interfaces/scheduler.interface.ts Public types for job timing unions, status shape, and scheduler contract.
src/index.ts Switches public exports from template “ExampleKit” to scheduler-kit API exports.
src/errors/duplicate-job.error.ts New error type thrown on duplicate job registration.
src/errors/duplicate-job.error.spec.ts Unit tests for DuplicateJobError.
src/decorators/scheduler.decorators.ts New @Cron, @Interval, @Timeout decorators and metadata types.
src/decorators/scheduler.decorators.spec.ts Unit tests verifying decorator metadata.
src/cron-expression.ts Adds human-readable cron expression constants.
src/cron-expression.spec.ts Unit tests for cron expression constants.
src/cron-builder.ts Adds fluent cron expression builder utilities.
src/cron-builder.spec.ts Unit tests for cron builder output and parsing behavior.
package.json Renames package, adjusts peer/dev deps, and adds tooling deps.
package-lock.json Lockfile updates reflecting new dependencies.
lint-staged.config.js Switches lint-staged config export style to CommonJS.
eslint.config.mjs Updates ignores and switches TS ESLint project configuration.
eslint.config.js Removes prior ESLint config file (replaced by eslint.config.mjs).
.husky/pre-push Updates hook commands (but removes shebang/bootstrap lines).
.github/workflows/release-check.yml Enables PR trigger and updates Sonar env values/formatting.
.github/workflows/publish.yml Enables push-to-master publish trigger and updates formatting.
.github/dependabot.yml Normalizes YAML quoting/formatting.

Comment on lines +58 to +65
reschedule(name: string, newTiming: ScheduleTiming): void {
const entry = this.registry.get(name);
if (!entry) return;
const newJob: ScheduledJob = { ...entry.job, ...newTiming } as ScheduledJob;
entry.stop();
this.registry.delete(name);
this.schedule(newJob);
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

reschedule() builds newJob by spreading the existing job and newTiming, which can leave the previous timing fields in place (e.g., rescheduling from cron → interval produces an object containing both cron and interval). Because _createEntry() checks the cron branch first, this can prevent rescheduling from actually changing the timing. Build newJob from { name, handler } plus newTiming (and explicitly omit the old timing fields) to preserve the discriminated-union invariant and ensure reschedules work across timing types.

Copilot uses AI. Check for mistakes.
// ─── Metadata Key ─────────────────────────────────────────────────────────────
// A unique symbol used as the key for storing scheduler metadata on methods.
// Using a unique constant prevents collisions with other decorator libraries.
export const CISCODE_SCHEDULER_METADATA = "ciscode:scheduler";
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

The metadata key comment says this is a "unique symbol", but CISCODE_SCHEDULER_METADATA is a string. Either change the comment to say "string key" or switch the constant to a Symbol(...) to match the documentation and reduce collision risk.

Suggested change
export const CISCODE_SCHEDULER_METADATA = "ciscode:scheduler";
export const CISCODE_SCHEDULER_METADATA = Symbol("ciscode:scheduler");

Copilot uses AI. Check for mistakes.
Comment thread src/cron-builder.ts
Comment on lines +60 to +61
* cron.every(5).minutes() // "* /5 * * * *"
* cron.every(2).hours() // "0 * /2 * * *"
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

The JSDoc examples show cron strings with spaces (e.g. "* /5 * * * *" and "0 * /2 * * "), which are not valid cron expressions and don’t match what cron.every() actually returns ("/5 * * * *" and "0 */2 * * *"). Update the examples to reflect the real output to avoid misleading consumers.

Suggested change
* cron.every(5).minutes() // "* /5 * * * *"
* cron.every(2).hours() // "0 * /2 * * *"
* cron.every(5).minutes() // "*/5 * * * *"
* cron.every(2).hours() // "0 */2 * * *"

Copilot uses AI. Check for mistakes.
Comment thread package.json
Comment on lines 60 to 66
"@nestjs/testing": "^10.4.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.7",
"cron": "^4.4.0",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

CronJob is imported from the cron package in the runtime code, but cron is only listed in devDependencies. This will cause consumers to get a runtime "Cannot find module 'cron'" when using the published package. Move cron to dependencies (or to peerDependencies plus documenting it) so it’s installed for consumers.

Copilot uses AI. Check for mistakes.
Comment thread package.json
"@nestjs/common": "^10 || ^11",
"@nestjs/core": "^10 || ^11",
"@nestjs/platform-express": "^10 || ^11",
"@nestjs/schedule": "^4 || ^5",
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

@nestjs/schedule is declared as a peer dependency with range ^4 || ^5, but the repo itself develops/tests with @nestjs/schedule@^6.1.1, and the source under src/ doesn’t appear to use @nestjs/schedule at all. Either remove it from peerDependencies if it’s not required, or widen the peer range to include the version you support and test against to avoid consumer install conflicts/warnings.

Suggested change
"@nestjs/schedule": "^4 || ^5",
"@nestjs/schedule": "^4 || ^5 || ^6",

Copilot uses AI. Check for mistakes.
Comment thread .husky/pre-push
Comment on lines 1 to 2
npm run typecheck
npm run test:cov No newline at end of file
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

This hook no longer has a shebang line (and no longer sources Husky’s helper script). Git executes hooks as standalone executables; without #!/usr/bin/env sh this can fail with an exec format error on Unix-like systems and break the pre-push checks. Restore the shebang (and Husky bootstrap if needed for your Husky version) so the hook runs reliably.

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +90
describe("reschedule()", () => {
it("changes the interval atomically", async () => {
const handler = jest.fn();
service.schedule({ name: "tick", handler, interval: 1000 });
service.reschedule("tick", { interval: 500 });
await jest.advanceTimersByTimeAsync(500); // tick 1 + flush
await jest.advanceTimersByTimeAsync(500); // tick 2 + flush
expect(handler).toHaveBeenCalledTimes(2);
});
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

reschedule() accepts any ScheduleTiming, but the test coverage here only verifies interval→interval changes. Add tests that reschedule between different timing types (e.g., cron→interval, interval→timeout) to ensure the old timing is fully replaced and only the new schedule triggers.

Copilot generated this review using guidance from repository custom instructions.
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.

4 participants