From e392cd753d845bed067b39d68a76375f4110808d Mon Sep 17 00:00:00 2001 From: Thibault Le Ouay Ducasse Date: Sat, 10 Oct 2026 21:22:13 +0200 Subject: [PATCH] test: move the test suites from deno test to vitest Run all 33 test packages on Vitest 5 instead of deno test with @std/expect and @std/testing: - @openstatus/test-utils becomes a shared vitest config (defineTestConfig) with a plugin that applies the existing test.importmap.json doubles with Deno import-map semantics, so no double changed - a codemod moves @std/testing/bdd + @std/expect to vitest, stub/spy to vi.spyOn/vi.fn, assertSpyCalls to toHaveBeenCalledTimes, .calls[i].args to .mock.calls[i]; FakeTime, returnsNext, Deno.serve/Command hand-ported - test files are typechecked again (tsc --noEmit), with the type errors that surfaced fixed - 10 unawaited expect().rejects assertions now await, so they actually run - the Bun-era compat shims (runner, mock(), matchers) and the dead server preload are removed; openapi:v1 runs on node Parity with the deno baseline was checked file by file: every test that ran under deno runs under vitest (5163 passed, 15 skipped). Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 2 +- apps/dashboard/package.json | 8 +- .../chat/tool-renderers/details.test.tsx | 5 +- .../chat/tool-renderers/get-audit-log.test.ts | 3 +- .../chat/tool-renderers/incidents.test.tsx | 3 +- .../chat/tool-renderers/list-tables.test.tsx | 3 +- .../maintenance-changes.test.ts | 3 +- .../chat/tool-renderers/registry.test.tsx | 22 +- .../status-report-changes.test.ts | 3 +- .../chat/tool-renderers/tables.test.tsx | 3 +- .../command-menu/groups/entities.test.ts | 3 +- .../command-menu/groups/scope.test.ts | 3 +- .../command-menu/groups/static.test.ts | 3 +- .../src/components/command-menu/types.test.ts | 3 +- .../components/common/changes-table.test.tsx | 3 +- .../components/common/status-badge.test.tsx | 3 +- .../data-table/table-cell-metadata.test.tsx | 3 +- .../incidents/incident-actions.test.ts | 3 +- .../onboarding/checks-table-adapter.test.ts | 3 +- .../src/components/ui/checkbox-tree.test.tsx | 3 +- .../dashboard/src/data/metrics.client.test.ts | 3 +- .../src/data/overview-events.client.test.ts | 3 +- .../src/data/page-components.client.test.ts | 3 +- apps/dashboard/src/data/status-codes.test.ts | 3 +- .../src/data/status-page-history.test.ts | 8 +- .../src/data/status-reports.client.test.ts | 3 +- apps/dashboard/src/lib/auth/providers.test.ts | 41 +- apps/dashboard/src/lib/composition.test.ts | 3 +- apps/dashboard/src/lib/domains.test.ts | 20 +- apps/dashboard/src/lib/formatter.test.ts | 12 +- .../src/lib/onboarding/email-domain.test.ts | 3 +- .../src/lib/rate-limit/magic-link.test.ts | 41 +- apps/dashboard/src/test/next-link.mock.tsx | 4 +- apps/dashboard/vitest.config.ts | 6 + apps/screenshot-service/tsconfig.json | 11 +- apps/server/AGENTS.md | 5 +- apps/server/package.json | 9 +- apps/server/src/libs/checker/utils.test.ts | 2 +- apps/server/src/libs/cli-telemetry.test.ts | 3 +- .../src/libs/middlewares/concurrency.test.ts | 3 +- .../src/libs/middlewares/credentials.test.ts | 3 +- .../src/libs/middlewares/rate-limit.test.ts | 14 +- .../libs/middlewares/require-scope.test.ts | 3 +- .../server/src/libs/middlewares/track.test.ts | 12 +- .../src/libs/middlewares/validate-key.test.ts | 3 +- apps/server/src/libs/sampling.test.ts | 8 +- .../src/libs/test/doubles/analytics.mock.ts | 10 +- apps/server/src/libs/test/doubles/state.ts | 18 +- apps/server/src/libs/test/preload.ts | 196 ----- apps/server/src/overload.test.ts | 5 +- apps/server/src/routes/mcp/adapter.test.ts | 3 +- apps/server/src/routes/mcp/handler.test.ts | 12 +- apps/server/src/routes/mcp/resources.test.ts | 27 +- .../routes/mcp/tools/register-scoped.test.ts | 3 +- .../server/src/routes/mcp/tools/tools.test.ts | 3 +- apps/server/src/routes/oauth/oauth.test.ts | 3 +- apps/server/src/routes/openapi.test.ts | 13 +- apps/server/src/routes/public/status.test.ts | 4 +- apps/server/src/routes/rpc/adapter.test.ts | 3 +- .../maintenance/__tests__/maintenance.test.ts | 16 +- .../monitor/__tests__/monitor.test.ts | 8 +- .../monitor/converters/assertions.test.ts | 3 +- .../monitor/converters/headers.test.ts | 3 +- .../monitor/converters/regions.test.ts | 3 +- .../handlers/monitor/response-logs.test.ts | 3 +- .../rpc/handlers/monitor/validators.test.ts | 3 +- .../__tests__/notification.test.ts | 3 +- .../__tests__/private-location.test.ts | 3 +- .../status-page/__tests__/status-page.test.ts | 14 +- .../__tests__/status-report.test.ts | 16 +- .../rpc/interceptors/__tests__/error.test.ts | 2 +- .../interceptors/__tests__/tracking.test.ts | 20 +- apps/server/src/routes/slack/blocks.test.ts | 3 +- .../src/routes/slack/channel-context.test.ts | 2 +- apps/server/src/routes/slack/commands.test.ts | 2 +- apps/server/src/routes/slack/config.ts | 5 +- .../routes/slack/confirmation-store.test.ts | 3 +- apps/server/src/routes/slack/handler.test.ts | 8 +- apps/server/src/routes/slack/home.test.ts | 3 +- .../routes/slack/incident-commands.test.ts | 3 +- apps/server/src/routes/slack/index.test.ts | 3 +- .../src/routes/slack/interactions.test.ts | 8 +- apps/server/src/routes/slack/mrkdwn.test.ts | 2 +- apps/server/src/routes/slack/oauth.test.ts | 3 +- .../slack/presenters/presenters.test.ts | 2 +- .../src/routes/slack/registry-runner.test.ts | 7 +- .../routes/slack/resolve-slack-user.test.ts | 3 +- .../server/src/routes/slack/rich-text.test.ts | 2 +- .../src/routes/slack/system-prompt.test.ts | 3 +- .../src/routes/slack/thread-title.test.ts | 2 +- apps/server/src/routes/slack/verify.test.ts | 3 +- .../src/routes/v1/check/http/post.test.ts | 4 +- .../src/routes/v1/incidents/get.test.ts | 3 +- .../src/routes/v1/incidents/get_all.test.ts | 3 +- .../src/routes/v1/incidents/put.test.ts | 3 +- .../v1/maintenances/attribution.test.ts | 3 +- .../src/routes/v1/maintenances/get.test.ts | 3 +- .../routes/v1/maintenances/get_all.test.ts | 3 +- .../src/routes/v1/maintenances/post.test.ts | 3 +- .../src/routes/v1/maintenances/put.test.ts | 46 +- .../src/routes/v1/monitors/delete.test.ts | 3 +- .../server/src/routes/v1/monitors/get.test.ts | 3 +- .../src/routes/v1/monitors/get_all.test.ts | 3 +- .../src/routes/v1/monitors/post.test.ts | 3 +- .../src/routes/v1/monitors/post_dns.test.ts | 3 +- .../src/routes/v1/monitors/post_http.test.ts | 3 +- .../src/routes/v1/monitors/post_tcp.test.ts | 3 +- .../server/src/routes/v1/monitors/put.test.ts | 3 +- .../src/routes/v1/monitors/put_dns.test.ts | 3 +- .../src/routes/v1/monitors/put_http.test.ts | 3 +- .../src/routes/v1/monitors/put_tcp.test.ts | 3 +- .../routes/v1/monitors/results/get.test.ts | 36 +- .../src/routes/v1/monitors/run/post.test.ts | 89 +- .../routes/v1/monitors/summary/get.test.ts | 5 +- .../routes/v1/monitors/trigger/post.test.ts | 4 +- .../src/routes/v1/notifications/get.test.ts | 3 +- .../routes/v1/notifications/get_all.test.ts | 3 +- .../src/routes/v1/notifications/post.test.ts | 3 +- .../routes/v1/pageSubscribers/post.test.ts | 29 +- apps/server/src/routes/v1/pages/get.test.ts | 3 +- .../src/routes/v1/pages/get_all.test.ts | 3 +- apps/server/src/routes/v1/pages/post.test.ts | 3 +- apps/server/src/routes/v1/pages/put.test.ts | 3 +- .../routes/v1/statusReportUpdates/get.test.ts | 3 +- .../v1/statusReportUpdates/post.test.ts | 3 +- .../v1/statusReports/attribution.test.ts | 3 +- .../routes/v1/statusReports/delete.test.ts | 3 +- .../src/routes/v1/statusReports/get.test.ts | 3 +- .../routes/v1/statusReports/get_all.test.ts | 3 +- .../src/routes/v1/statusReports/post.test.ts | 3 +- .../subscriber-filtering.integration.test.ts | 3 +- .../v1/statusReports/update/post.test.ts | 3 +- apps/server/src/routes/v1/whoami/get.test.ts | 3 +- apps/server/tsconfig.json | 18 +- apps/server/vitest.config.ts | 6 + apps/status-page/package.json | 8 +- .../src/components/button/buttons.test.tsx | 3 +- .../src/components/common/link.test.tsx | 3 +- .../content/process-message.test.tsx | 5 +- .../src/components/date-picker.test.tsx | 3 +- .../forms/form-manage-subscription.test.tsx | 3 +- .../src/components/forms/forms.test.tsx | 3 +- .../src/components/locale-switcher.test.tsx | 3 +- .../status-component-group.test.tsx | 3 +- .../status-page/status-feed.test.tsx | 3 +- .../src/components/status-page/utils.test.ts | 3 +- .../themes/theme-builder-provider.test.tsx | 3 +- .../data-table/data-table-pagination.test.tsx | 3 +- .../src/content/markdown/generators.test.ts | 3 +- .../src/content/markdown/helpers.test.ts | 3 +- .../src/content/markdown/match-route.test.ts | 3 +- .../src/content/status-json.test.ts | 3 +- .../src/content/status-vocab.test.ts | 3 +- .../src/data/actions.client.test.ts | 3 +- .../src/data/metrics.client.test.ts | 3 +- apps/status-page/src/i18n/messages.test.ts | 3 +- apps/status-page/src/lib/alternates.test.ts | 3 +- apps/status-page/src/lib/base-url.test.ts | 3 +- apps/status-page/src/lib/chart.test.ts | 3 +- apps/status-page/src/lib/composition.test.ts | 3 +- apps/status-page/src/lib/domain.test.ts | 3 +- apps/status-page/src/lib/embed-params.test.ts | 3 +- apps/status-page/src/lib/formatter.test.ts | 3 +- apps/status-page/src/lib/http/etag.test.ts | 3 +- .../src/lib/http/markdown-response.test.ts | 3 +- .../proxy/apply-page-locale-override.test.ts | 3 +- .../lib/proxy/apply-page-slug-prefix.test.ts | 3 +- .../src/lib/proxy/build-external-path.test.ts | 3 +- .../src/lib/proxy/compose-page-action.test.ts | 3 +- .../src/lib/proxy/detect-markdown.test.ts | 3 +- .../lib/proxy/markdown-cache-control.test.ts | 3 +- .../src/lib/proxy/proxy-chain.test.ts | 3 +- .../lib/proxy/resolve-default-rewrite.test.ts | 3 +- .../proxy/resolve-email-domain-action.test.ts | 3 +- .../resolve-ip-restriction-action.test.ts | 3 +- .../lib/proxy/resolve-locale-action.test.ts | 3 +- .../lib/proxy/resolve-password-action.test.ts | 3 +- .../resolve-unresolved-host-action.test.ts | 3 +- .../lib/proxy/sanitize-redirect-param.test.ts | 3 +- .../proxy/strip-prefix-for-external.test.ts | 3 +- .../src/lib/report-impacts.test.ts | 3 +- .../src/lib/resolve-pathname-prefix.test.ts | 3 +- .../status-page/src/lib/resolve-route.test.ts | 3 +- .../src/lib/status-blocks-labels.test.ts | 3 +- apps/status-page/src/lib/theme-draft.test.ts | 22 +- .../src/lib/theme-explorer-host.test.ts | 3 +- apps/status-page/src/lib/theme-prompt.test.ts | 3 +- apps/status-page/src/test/next-link.mock.tsx | 4 +- apps/status-page/vitest.config.ts | 5 + apps/web/package.json | 8 +- apps/web/src/app/(ai)/mcp/route.test.ts | 3 +- .../app/(landing)/play/checker/parse.test.ts | 3 +- .../(landing)/play/mcp-health/utils.test.ts | 3 +- .../(landing)/status/incident-events.test.ts | 3 +- .../src/app/(landing)/status/utils.test.ts | 3 +- apps/web/src/content/agent-recovery.test.ts | 3 +- apps/web/src/content/convert.test.ts | 3 +- .../mdx-components/content-lint.test.ts | 3 +- .../content/mdx-components/details.test.ts | 3 +- .../src/content/utils/search-match.test.ts | 3 +- apps/web/src/lib/cdn-checker/probe.test.ts | 3 +- apps/web/src/lib/cdn-checker/schema.test.ts | 3 +- apps/web/src/lib/cdn-checker/summary.test.ts | 3 +- apps/web/src/lib/checker/utils.test.ts | 3 +- apps/web/src/lib/mcp/health-check.test.ts | 3 +- .../src/lib/metadata/shared-metadata.test.ts | 3 +- .../src/lib/metadata/structured-data.test.ts | 3 +- apps/web/src/lib/monitor/utils.test.ts | 8 +- apps/web/src/lib/ratelimit-config.test.ts | 3 +- apps/web/src/lib/utils.test.ts | 7 +- apps/web/vitest.config.ts | 3 + apps/workflows/AGENTS.md | 10 +- apps/workflows/package.json | 5 +- apps/workflows/src/checker/alerting.test.ts | 46 +- apps/workflows/src/checker/outbox.test.ts | 70 +- .../src/checker/private-location.test.ts | 57 +- apps/workflows/src/checker/transition.test.ts | 8 +- .../src/checker/update-status.test.ts | 25 +- .../src/cron/external-status-detect.test.ts | 2 +- .../src/cron/private-location-health.test.ts | 37 +- apps/workflows/src/cron/scheduler.test.ts | 2 +- apps/workflows/src/cron/status-drift.test.ts | 26 +- apps/workflows/src/test/stub-audit-log.ts | 21 + apps/workflows/tsconfig.json | 11 +- apps/workflows/vitest.config.ts | 3 + packages/ai/package.json | 8 +- packages/ai/src/resolve-model.test.ts | 3 +- packages/ai/tsconfig.json | 11 +- packages/ai/vitest.config.ts | 3 + packages/analytics/tsconfig.json | 11 +- packages/api/package.json | 8 +- packages/api/src/router/blob.test.ts | 3 +- packages/api/src/router/checker.test.ts | 3 +- packages/api/src/router/domain.test.ts | 3 +- .../api/src/router/effective-status.test.ts | 3 +- packages/api/src/router/import.test.ts | 3 +- packages/api/src/router/incident.test.ts | 3 +- packages/api/src/router/maintenance.test.ts | 3 +- packages/api/src/router/monitor.test.ts | 3 +- packages/api/src/router/notification.test.ts | 3 +- packages/api/src/router/oauth.test.ts | 3 +- packages/api/src/router/pageComponent.test.ts | 3 +- .../api/src/router/privateLocation.test.ts | 3 +- packages/api/src/router/slackUser.test.ts | 3 +- .../api/src/router/statusPage.access.test.ts | 3 +- .../api/src/router/statusPage.e2e.test.ts | 3 +- .../src/router/statusPage.unsubscribe.test.ts | 3 +- .../api/src/router/statusPage.utils.test.ts | 7 +- packages/api/src/router/statusReport.test.ts | 3 +- .../src/router/stripe/trial-lifecycle.test.ts | 240 +++--- packages/api/src/router/stripe/trial.test.ts | 128 +-- packages/api/src/router/stripe/utils.test.ts | 3 +- .../api/src/router/stripe/webhook.test.ts | 164 ++-- .../api/src/router/tinybird/index.test.ts | 3 +- packages/api/src/router/workspace.test.ts | 3 +- packages/api/src/test/upstash.mock.ts | 4 +- packages/api/src/trpc-errors.test.ts | 3 +- packages/api/src/trpc.test.ts | 3 +- packages/api/tsconfig.json | 11 +- packages/api/vitest.config.ts | 6 + packages/assertions/package.json | 8 +- packages/assertions/src/serializing.test.ts | 3 +- packages/assertions/src/v1.test.ts | 9 +- packages/assertions/tsconfig.json | 11 +- packages/assertions/vitest.config.ts | 3 + packages/db/package.json | 5 +- packages/db/script/region-migration.test.ts | 3 +- packages/db/src/schema/plan/utils.test.ts | 3 +- packages/db/src/utils/api-key.test.ts | 3 +- packages/db/tsconfig.json | 10 +- packages/emails/package.json | 8 +- packages/emails/src/billing.test.tsx | 101 ++- .../emails/src/client.integration.test.tsx | 3 +- packages/emails/src/client.test.ts | 100 ++- packages/emails/src/format.test.ts | 3 +- packages/emails/src/templates.test.tsx | 3 +- packages/emails/src/test-preload.ts | 3 +- packages/emails/tsconfig.json | 12 +- packages/emails/vitest.config.ts | 3 + packages/error/tsconfig.json | 11 +- packages/header-analysis/package.json | 10 +- .../src/cdn/detect-cdn.test.ts | 3 +- .../header-analysis/src/cdn/edge-pop.test.ts | 3 +- .../src/cdn/infer-topology.test.ts | 3 +- .../src/cdn/normalize-cache-status.test.ts | 3 +- .../src/parser/cache-control.test.ts | 3 +- .../src/parser/cf-cache-status.test.ts | 3 +- .../header-analysis/src/parser/cf-ray.test.ts | 3 +- .../src/parser/fly-request-id.test.ts | 3 +- .../src/parser/x-vercel-cache.test.ts | 3 +- .../src/parser/x-vercel-id.test.ts | 3 +- packages/header-analysis/tsconfig.json | 11 +- packages/header-analysis/vitest.config.ts | 3 + packages/icons/tsconfig.json | 12 +- packages/importers/package.json | 10 +- .../src/providers/betterstack/client.test.ts | 24 +- .../src/providers/betterstack/mapper.test.ts | 3 +- .../providers/betterstack/provider.test.ts | 8 +- .../src/providers/checkly/client.test.ts | 32 +- .../src/providers/checkly/mapper.test.ts | 3 +- .../src/providers/checkly/provider.test.ts | 6 +- .../src/providers/instatus/client.test.ts | 26 +- .../src/providers/instatus/mapper.test.ts | 3 +- .../src/providers/instatus/provider.test.ts | 3 +- .../src/providers/statuspage/client.test.ts | 26 +- .../src/providers/statuspage/mapper.test.ts | 3 +- .../src/providers/statuspage/provider.test.ts | 3 +- packages/importers/src/schemas.test.ts | 3 +- packages/importers/tsconfig.json | 11 +- packages/importers/vitest.config.ts | 3 + packages/locales/tsconfig.json | 11 +- packages/notifications/base/package.json | 8 +- .../base/src/utils/duration.test.ts | 3 +- .../base/src/utils/incident.test.ts | 3 +- .../base/src/utils/timestamp.test.ts | 3 +- packages/notifications/base/tsconfig.json | 9 +- packages/notifications/base/vitest.config.ts | 3 + .../notifications/bird-whatsapp/package.json | 8 +- .../bird-whatsapp/src/index.test.ts | 40 +- .../notifications/bird-whatsapp/tsconfig.json | 11 +- .../bird-whatsapp/vitest.config.ts | 3 + packages/notifications/discord/package.json | 8 +- .../notifications/discord/src/index.test.ts | 48 +- packages/notifications/discord/tsconfig.json | 11 +- .../notifications/discord/vitest.config.ts | 3 + packages/notifications/email/package.json | 5 +- .../notifications/email/src/index.test.ts | 39 +- .../notifications/email/src/test-preload.ts | 3 +- packages/notifications/email/tsconfig.json | 11 +- packages/notifications/email/vitest.config.ts | 3 + .../notifications/google-chat/package.json | 8 +- .../google-chat/src/index.test.ts | 52 +- .../notifications/google-chat/tsconfig.json | 11 +- .../google-chat/vitest.config.ts | 3 + .../notifications/grafana-oncall/package.json | 8 +- .../grafana-oncall/src/index.test.ts | 82 +- .../grafana-oncall/tsconfig.json | 11 +- .../grafana-oncall/vitest.config.ts | 3 + packages/notifications/ms-teams/package.json | 8 +- .../notifications/ms-teams/src/index.test.ts | 43 +- packages/notifications/ms-teams/tsconfig.json | 11 +- .../notifications/ms-teams/vitest.config.ts | 3 + packages/notifications/ntfy/package.json | 8 +- packages/notifications/ntfy/src/index.test.ts | 84 +- packages/notifications/ntfy/tsconfig.json | 11 +- packages/notifications/ntfy/vitest.config.ts | 3 + packages/notifications/opsgenie/package.json | 8 +- .../notifications/opsgenie/src/index.test.ts | 92 +- packages/notifications/opsgenie/tsconfig.json | 11 +- .../notifications/opsgenie/vitest.config.ts | 3 + packages/notifications/pagerduty/package.json | 8 +- .../notifications/pagerduty/src/index.test.ts | 68 +- .../notifications/pagerduty/tsconfig.json | 11 +- .../notifications/pagerduty/vitest.config.ts | 3 + packages/notifications/slack/package.json | 8 +- .../notifications/slack/src/index.test.ts | 66 +- packages/notifications/slack/tsconfig.json | 11 +- packages/notifications/slack/vitest.config.ts | 3 + packages/notifications/telegram/package.json | 8 +- .../notifications/telegram/src/index.test.ts | 70 +- packages/notifications/telegram/tsconfig.json | 11 +- .../notifications/telegram/vitest.config.ts | 3 + .../notifications/twillio-sms/package.json | 8 +- .../twillio-sms/src/index.test.ts | 52 +- .../notifications/twillio-sms/tsconfig.json | 11 +- .../twillio-sms/vitest.config.ts | 3 + packages/notifications/webhook/package.json | 8 +- .../notifications/webhook/src/index.test.ts | 116 ++- packages/notifications/webhook/tsconfig.json | 10 +- .../notifications/webhook/vitest.config.ts | 3 + packages/proto/tsconfig.json | 10 +- packages/regions/tsconfig.json | 11 +- packages/services/package.json | 8 +- .../src/__tests__/attribution.test.ts | 3 +- .../services/src/__tests__/context.test.ts | 3 +- .../services/src/__tests__/features.test.ts | 3 +- .../services/src/__tests__/limits.test.ts | 3 +- packages/services/src/__tests__/retry.test.ts | 3 +- .../__tests__/with-test-transaction.test.ts | 3 +- .../src/__tests__/with-transaction.test.ts | 3 +- .../src/agent-tools/__tests__/content.test.ts | 16 +- .../src/agent-tools/__tests__/docs.test.ts | 8 +- .../agent-tools/__tests__/incident.test.ts | 3 +- .../__tests__/maintenance-updates.test.ts | 3 +- .../src/agent-tools/__tests__/prompt.test.ts | 3 +- .../__tests__/shape-equivalence.test.ts | 3 +- .../__tests__/status-report-impacts.test.ts | 3 +- .../__tests__/summary-refs.test.ts | 3 +- .../src/api-key/__tests__/api-key.test.ts | 3 +- .../src/audit/__tests__/audit.test.ts | 3 +- .../src/auth/__tests__/matches-scope.test.ts | 3 +- .../src/auth/__tests__/require-role.test.ts | 3 +- .../src/auth/__tests__/require-scope.test.ts | 3 +- .../external-service-component.test.ts | 3 +- .../__tests__/internal.test.ts | 3 +- .../external-service-incident.test.ts | 3 +- .../__tests__/record.test.ts | 3 +- .../__tests__/external-service.test.ts | 3 +- .../__tests__/update-provider.test.ts | 3 +- .../frozen-uptime/__tests__/compute.test.ts | 3 +- .../frozen-uptime/__tests__/freeze.test.ts | 3 +- .../__tests__/get-history.test.ts | 3 +- .../src/frozen-uptime/__tests__/run.test.ts | 3 +- .../frozen-uptime/__tests__/schemas.test.ts | 3 +- .../src/import/__tests__/attribution.test.ts | 3 +- .../import/__tests__/incidents-phase.test.ts | 3 +- .../src/import/__tests__/unit.test.ts | 3 +- .../src/incident/__tests__/hooks.test.ts | 3 +- .../src/incident/__tests__/incident.test.ts | 3 +- .../__tests__/postmortem-draft.test.ts | 3 +- .../src/incident/__tests__/postmortem.test.ts | 3 +- .../src/incident/__tests__/reminders.test.ts | 3 +- .../src/incident/__tests__/slack-flow.test.ts | 3 +- .../__tests__/install-slack-agent.test.ts | 3 +- .../__tests__/slack-scopes.test.ts | 3 +- .../__tests__/uninstall-slack-agent.test.ts | 3 +- .../invitation/__tests__/invitation.test.ts | 3 +- .../maintenance/__tests__/attribution.test.ts | 3 +- .../maintenance/__tests__/maintenance.test.ts | 4 +- .../src/member/__tests__/display-name.test.ts | 3 +- .../member/__tests__/find-by-email.test.ts | 3 +- .../src/member/__tests__/member.test.ts | 3 +- .../__tests__/monitor-incident.test.ts | 3 +- .../monitor-tag/__tests__/monitor-tag.test.ts | 3 +- .../src/monitor/__tests__/monitor.test.ts | 3 +- .../src/monitor/__tests__/reads.test.ts | 3 +- .../__tests__/response-logs-cursor.test.ts | 3 +- .../__tests__/response-logs-internal.test.ts | 3 +- .../__tests__/stream-monitor-preview.test.ts | 3 +- .../src/monitor/__tests__/url-safety.test.ts | 3 +- .../__tests__/notification.test.ts | 3 +- .../services/src/oauth/__tests__/cimd.test.ts | 3 +- .../src/oauth/__tests__/crypto.test.ts | 3 +- .../src/oauth/__tests__/metadata.test.ts | 3 +- .../src/oauth/__tests__/oauth.test.ts | 3 +- .../__tests__/redirect-allowlist.test.ts | 3 +- .../src/oauth/__tests__/schemas.test.ts | 3 +- .../src/page-access/access-predicates.test.ts | 3 +- .../src/page-access/client-ip.test.ts | 3 +- .../evaluate-markdown-gate.test.ts | 3 +- .../src/page-access/is-ip-allowed.test.ts | 3 +- .../__tests__/page-component-group.test.ts | 3 +- .../__tests__/get-daily-summary.test.ts | 3 +- .../__tests__/page-component.test.ts | 3 +- .../__tests__/page-subscriber.test.ts | 34 +- .../page-subscriber/__tests__/slack.test.ts | 3 +- .../services/src/page/__tests__/page.test.ts | 3 +- .../__tests__/private-location.test.ts | 3 +- .../slack-user/__tests__/slack-user.test.ts | 3 +- .../services/src/sso/__tests__/sso.test.ts | 3 +- .../__tests__/attribution.test.ts | 3 +- .../__tests__/derive-status.test.ts | 3 +- .../__tests__/status-report.test.ts | 4 +- .../__tests__/downtime.test.ts | 3 +- .../status-timeline/__tests__/events.test.ts | 12 +- .../status-timeline/__tests__/uptime.test.ts | 3 +- .../src/workspace/__tests__/billing.test.ts | 3 +- .../src/workspace/__tests__/downgrade.test.ts | 3 +- .../src/workspace/__tests__/trial.test.ts | 3 +- .../src/workspace/__tests__/workspace.test.ts | 3 +- packages/services/test/helpers.ts | 2 +- packages/services/tsconfig.json | 12 +- packages/services/vitest.config.ts | 3 + .../status-fetcher/__tests__/detect.test.ts | 13 +- .../status-fetcher/__tests__/fetch.test.ts | 16 +- .../__tests__/fetchers/atlassian.test.ts | 21 +- .../__tests__/fetchers/betterstack.test.ts | 15 +- .../__tests__/fetchers/components.test.ts | 11 +- .../__tests__/fetchers/custom.test.ts | 3 +- .../__tests__/fetchers/edge-cases.test.ts | 3 +- .../__tests__/fetchers/html.test.ts | 9 +- .../__tests__/fetchers/incidentio.test.ts | 23 +- .../__tests__/fetchers/instatus.test.ts | 15 +- .../__tests__/fetchers/uptimerobot.test.ts | 14 +- packages/status-fetcher/__tests__/helpers.ts | 4 +- .../__tests__/integration.test.ts | 3 +- .../status-fetcher/__tests__/utils.test.ts | 3 +- packages/status-fetcher/package.json | 10 +- packages/status-fetcher/tsconfig.json | 11 +- packages/status-fetcher/vitest.config.ts | 3 + packages/subscriptions/package.json | 8 +- .../subscriptions/src/channels/email.test.ts | 62 +- .../subscriptions/src/channels/index.test.ts | 3 +- .../src/channels/slack-blocks.test.ts | 3 +- .../subscriptions/src/channels/slack.test.ts | 3 +- .../src/channels/webhook.test.ts | 72 +- packages/subscriptions/src/dispatcher.test.ts | 51 +- packages/subscriptions/src/test-preload.ts | 3 +- packages/subscriptions/tsconfig.json | 12 +- packages/subscriptions/vitest.config.ts | 3 + packages/test-utils/package.json | 9 +- packages/test-utils/src/index.ts | 27 - packages/test-utils/src/matchers.ts | 95 -- packages/test-utils/src/mock-fn.ts | 112 --- packages/test-utils/src/runner.ts | 58 -- packages/test-utils/src/vitest.ts | 91 ++ packages/test-utils/tsconfig.json | 4 + packages/theme-store/package.json | 8 +- .../src/__tests__/custom-theme.test.ts | 3 +- .../src/__tests__/theme-io.test.ts | 3 +- packages/theme-store/tsconfig.json | 11 +- packages/theme-store/vitest.config.ts | 3 + packages/tinybird/package.json | 8 +- packages/tinybird/src/client.test.ts | 3 +- packages/tinybird/src/schema.test.ts | 3 +- packages/tinybird/tsconfig.json | 10 +- packages/tinybird/vitest.config.ts | 3 + packages/tracker/package.json | 8 +- packages/tracker/src/tracker.test.ts | 3 +- packages/tracker/src/utils.test.ts | 3 +- packages/tracker/tsconfig.json | 11 +- packages/tracker/vitest.config.ts | 3 + packages/upstash/package.json | 8 +- .../upstash/src/redis/incr-with-ttl.test.ts | 3 +- packages/upstash/tsconfig.json | 10 +- packages/upstash/vitest.config.ts | 3 + packages/utils/package.json | 8 +- packages/utils/src/curl.test.ts | 69 +- packages/utils/src/headers.test.ts | 3 +- packages/utils/src/payloads.test.ts | 3 +- packages/utils/src/person-name.test.ts | 3 +- packages/utils/src/ssrf.test.ts | 3 +- packages/utils/src/status.test.ts | 3 +- packages/utils/src/stream.test.ts | 3 +- packages/utils/tsconfig.json | 11 +- packages/utils/vitest.config.ts | 3 + pnpm-lock.yaml | 808 ++++++++++++------ pnpm-workspace.yaml | 2 + 528 files changed, 2976 insertions(+), 3309 deletions(-) create mode 100644 apps/dashboard/vitest.config.ts delete mode 100644 apps/server/src/libs/test/preload.ts create mode 100644 apps/server/vitest.config.ts create mode 100644 apps/status-page/vitest.config.ts create mode 100644 apps/web/vitest.config.ts create mode 100644 apps/workflows/src/test/stub-audit-log.ts create mode 100644 apps/workflows/vitest.config.ts create mode 100644 packages/ai/vitest.config.ts create mode 100644 packages/api/vitest.config.ts create mode 100644 packages/assertions/vitest.config.ts create mode 100644 packages/emails/vitest.config.ts create mode 100644 packages/header-analysis/vitest.config.ts create mode 100644 packages/importers/vitest.config.ts create mode 100644 packages/notifications/base/vitest.config.ts create mode 100644 packages/notifications/bird-whatsapp/vitest.config.ts create mode 100644 packages/notifications/discord/vitest.config.ts create mode 100644 packages/notifications/email/vitest.config.ts create mode 100644 packages/notifications/google-chat/vitest.config.ts create mode 100644 packages/notifications/grafana-oncall/vitest.config.ts create mode 100644 packages/notifications/ms-teams/vitest.config.ts create mode 100644 packages/notifications/ntfy/vitest.config.ts create mode 100644 packages/notifications/opsgenie/vitest.config.ts create mode 100644 packages/notifications/pagerduty/vitest.config.ts create mode 100644 packages/notifications/slack/vitest.config.ts create mode 100644 packages/notifications/telegram/vitest.config.ts create mode 100644 packages/notifications/twillio-sms/vitest.config.ts create mode 100644 packages/notifications/webhook/vitest.config.ts create mode 100644 packages/services/vitest.config.ts create mode 100644 packages/status-fetcher/vitest.config.ts create mode 100644 packages/subscriptions/vitest.config.ts delete mode 100644 packages/test-utils/src/index.ts delete mode 100644 packages/test-utils/src/matchers.ts delete mode 100644 packages/test-utils/src/mock-fn.ts delete mode 100644 packages/test-utils/src/runner.ts create mode 100644 packages/test-utils/src/vitest.ts create mode 100644 packages/test-utils/tsconfig.json create mode 100644 packages/theme-store/vitest.config.ts create mode 100644 packages/tinybird/vitest.config.ts create mode 100644 packages/tracker/vitest.config.ts create mode 100644 packages/upstash/vitest.config.ts create mode 100644 packages/utils/vitest.config.ts diff --git a/AGENTS.md b/AGENTS.md index e75dcadcd..ad7c2f4b6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,7 +19,7 @@ are in `apps/dashboard/README.md`. `devbox.json` pins exact node, deno, bun, turso-cli and sqld versions; the CI workflows pin the same node and deno. Never float one back to `@latest` or -`v2.x` — `tsc` and `deno test` results differ by version, so a drifting pin makes +`v2.x` — `tsc` and test results differ by version, so a drifting pin makes `pnpm verify` disagree with CI for reasons unrelated to your change. ## Architecture diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index a3c5a88e5..0acad7ea6 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -9,7 +9,7 @@ "lint": "next lint", "check": "tsc --noEmit", "tsc": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports --import-map=test.importmap.json src" + "test": "vitest run" }, "dependencies": { "@ai-sdk/react": "catalog:", @@ -107,8 +107,7 @@ "zod": "catalog:" }, "devDependencies": { - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", + "@openstatus/test-utils": "workspace:*", "@tailwindcss/postcss": "catalog:", "@tailwindcss/typography": "catalog:", "@types/dom-speech-recognition": "catalog:", @@ -118,6 +117,7 @@ "shadcn": "catalog:", "tailwindcss": "catalog:", "tw-animate-css": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/apps/dashboard/src/components/chat/tool-renderers/details.test.tsx b/apps/dashboard/src/components/chat/tool-renderers/details.test.tsx index 3306be180..e90245027 100644 --- a/apps/dashboard/src/components/chat/tool-renderers/details.test.tsx +++ b/apps/dashboard/src/components/chat/tool-renderers/details.test.tsx @@ -1,7 +1,6 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import type { ReactNode } from "react"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import type { DetailsTableData } from "./details-table"; import { getMonitorDetails } from "./get-monitor"; @@ -187,7 +186,7 @@ describe("getResponseLogDetails", () => { }); // statusCode is nullable, but TableCellNumber renders Number(null) as 0. - it.ignore("does not show 0 for a missing status code", () => { + it.skip("does not show 0 for a missing status code", () => { const data = getResponseLogDetails( input, asFixture({ ...log, statusCode: null }), diff --git a/apps/dashboard/src/components/chat/tool-renderers/get-audit-log.test.ts b/apps/dashboard/src/components/chat/tool-renderers/get-audit-log.test.ts index a7ed3b83a..47c58b113 100644 --- a/apps/dashboard/src/components/chat/tool-renderers/get-audit-log.test.ts +++ b/apps/dashboard/src/components/chat/tool-renderers/get-audit-log.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { getAuditLogChanges } from "./get-audit-log"; diff --git a/apps/dashboard/src/components/chat/tool-renderers/incidents.test.tsx b/apps/dashboard/src/components/chat/tool-renderers/incidents.test.tsx index cd2ee601c..f8cfbc84d 100644 --- a/apps/dashboard/src/components/chat/tool-renderers/incidents.test.tsx +++ b/apps/dashboard/src/components/chat/tool-renderers/incidents.test.tsx @@ -1,7 +1,6 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import type { ReactNode } from "react"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { declareIncidentChanges, diff --git a/apps/dashboard/src/components/chat/tool-renderers/list-tables.test.tsx b/apps/dashboard/src/components/chat/tool-renderers/list-tables.test.tsx index 08a247974..6e83bd492 100644 --- a/apps/dashboard/src/components/chat/tool-renderers/list-tables.test.tsx +++ b/apps/dashboard/src/components/chat/tool-renderers/list-tables.test.tsx @@ -1,7 +1,6 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import type { ReactNode } from "react"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { listMonitorsTable } from "./list-monitors"; import { listNotificationsTable } from "./list-notifications"; diff --git a/apps/dashboard/src/components/chat/tool-renderers/maintenance-changes.test.ts b/apps/dashboard/src/components/chat/tool-renderers/maintenance-changes.test.ts index d3f5c954f..106b865af 100644 --- a/apps/dashboard/src/components/chat/tool-renderers/maintenance-changes.test.ts +++ b/apps/dashboard/src/components/chat/tool-renderers/maintenance-changes.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { createMaintenanceChanges } from "./create-maintenance"; import { diff --git a/apps/dashboard/src/components/chat/tool-renderers/registry.test.tsx b/apps/dashboard/src/components/chat/tool-renderers/registry.test.tsx index 40e7a274a..0685c0baa 100644 --- a/apps/dashboard/src/components/chat/tool-renderers/registry.test.tsx +++ b/apps/dashboard/src/components/chat/tool-renderers/registry.test.tsx @@ -1,7 +1,13 @@ -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, it } from "@std/testing/bdd"; -import { type Stub, stub } from "@std/testing/mock"; import { renderToStaticMarkup } from "react-dom/server"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, + type MockInstance, +} from "vitest"; import { renderToolDraft, @@ -10,11 +16,13 @@ import { toolRenderers, } from "./index"; -let warnStub: Stub | undefined; +let warnStub: MockInstance | undefined; beforeEach(() => { - warnStub = stub(console, "warn"); + warnStub = vi + .spyOn(console, "warn") + .mockImplementation(() => undefined as never); }); -afterEach(() => warnStub?.restore()); +afterEach(() => warnStub?.mockRestore()); describe("summarizeToolOutput", () => { it("pluralises item counts", () => { @@ -129,7 +137,7 @@ describe("unknown tools", () => { expect(summarizeToolOutput("not_a_tool", {})).toBeUndefined(); expect(renderToolDraft("not_a_tool", {})).toBeUndefined(); expect(renderToolResult("not_a_tool", {}, {})).toBeUndefined(); - expect(warnStub?.calls).toHaveLength(1); + expect(warnStub?.mock.calls).toHaveLength(1); }); }); diff --git a/apps/dashboard/src/components/chat/tool-renderers/status-report-changes.test.ts b/apps/dashboard/src/components/chat/tool-renderers/status-report-changes.test.ts index 1e7417799..799b80719 100644 --- a/apps/dashboard/src/components/chat/tool-renderers/status-report-changes.test.ts +++ b/apps/dashboard/src/components/chat/tool-renderers/status-report-changes.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { addStatusReportUpdateChanges } from "./add-status-report-update"; import { createStatusReportChanges } from "./create-status-report"; diff --git a/apps/dashboard/src/components/chat/tool-renderers/tables.test.tsx b/apps/dashboard/src/components/chat/tool-renderers/tables.test.tsx index d5b601a22..4b79a1732 100644 --- a/apps/dashboard/src/components/chat/tool-renderers/tables.test.tsx +++ b/apps/dashboard/src/components/chat/tool-renderers/tables.test.tsx @@ -1,7 +1,6 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import type { ReactNode } from "react"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { DetailsTable } from "./details-table"; import { getMonitorStatusTable } from "./get-monitor-status"; diff --git a/apps/dashboard/src/components/command-menu/groups/entities.test.ts b/apps/dashboard/src/components/command-menu/groups/entities.test.ts index 213981dde..41ac4b4cb 100644 --- a/apps/dashboard/src/components/command-menu/groups/entities.test.ts +++ b/apps/dashboard/src/components/command-menu/groups/entities.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { incidentsGroup, diff --git a/apps/dashboard/src/components/command-menu/groups/scope.test.ts b/apps/dashboard/src/components/command-menu/groups/scope.test.ts index 31ce56dd8..20e7ae915 100644 --- a/apps/dashboard/src/components/command-menu/groups/scope.test.ts +++ b/apps/dashboard/src/components/command-menu/groups/scope.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { MONITOR_TABS } from "@/app/(dashboard)/monitors/[id]/constants"; import { STATUS_PAGE_TABS } from "@/app/(dashboard)/status-pages/[id]/constants"; diff --git a/apps/dashboard/src/components/command-menu/groups/static.test.ts b/apps/dashboard/src/components/command-menu/groups/static.test.ts index 4df096607..70d751382 100644 --- a/apps/dashboard/src/components/command-menu/groups/static.test.ts +++ b/apps/dashboard/src/components/command-menu/groups/static.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { HELP_LINKS } from "@/config/help"; import { SETTINGS_TABS } from "@/config/settings"; diff --git a/apps/dashboard/src/components/command-menu/types.test.ts b/apps/dashboard/src/components/command-menu/types.test.ts index 7f3ae5965..4a7aacc39 100644 --- a/apps/dashboard/src/components/command-menu/types.test.ts +++ b/apps/dashboard/src/components/command-menu/types.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { pageLabel } from "./types"; diff --git a/apps/dashboard/src/components/common/changes-table.test.tsx b/apps/dashboard/src/components/common/changes-table.test.tsx index f3aff5032..ef19b0aa6 100644 --- a/apps/dashboard/src/components/common/changes-table.test.tsx +++ b/apps/dashboard/src/components/common/changes-table.test.tsx @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { ChangesTable, diff --git a/apps/dashboard/src/components/common/status-badge.test.tsx b/apps/dashboard/src/components/common/status-badge.test.tsx index e504b0430..802465db0 100644 --- a/apps/dashboard/src/components/common/status-badge.test.tsx +++ b/apps/dashboard/src/components/common/status-badge.test.tsx @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { StatusBadge } from "./status-badge"; diff --git a/apps/dashboard/src/components/data-table/table-cell-metadata.test.tsx b/apps/dashboard/src/components/data-table/table-cell-metadata.test.tsx index 87eb14ab9..9b7c128a6 100644 --- a/apps/dashboard/src/components/data-table/table-cell-metadata.test.tsx +++ b/apps/dashboard/src/components/data-table/table-cell-metadata.test.tsx @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { TableCellMetadata, getMetadataEntries } from "./table-cell-metadata"; diff --git a/apps/dashboard/src/components/incidents/incident-actions.test.ts b/apps/dashboard/src/components/incidents/incident-actions.test.ts index 6c3ea1d96..3ac8c6f3d 100644 --- a/apps/dashboard/src/components/incidents/incident-actions.test.ts +++ b/apps/dashboard/src/components/incidents/incident-actions.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { hasIncidentActions } from "./incident-actions"; diff --git a/apps/dashboard/src/components/onboarding/checks-table-adapter.test.ts b/apps/dashboard/src/components/onboarding/checks-table-adapter.test.ts index f47183b5d..fd61bb6b8 100644 --- a/apps/dashboard/src/components/onboarding/checks-table-adapter.test.ts +++ b/apps/dashboard/src/components/onboarding/checks-table-adapter.test.ts @@ -1,6 +1,5 @@ import type { CheckResult } from "@openstatus/services/monitor"; -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { checkResultToResponseLog } from "./checks-table-adapter"; diff --git a/apps/dashboard/src/components/ui/checkbox-tree.test.tsx b/apps/dashboard/src/components/ui/checkbox-tree.test.tsx index 642cd27c4..9c1600e04 100644 --- a/apps/dashboard/src/components/ui/checkbox-tree.test.tsx +++ b/apps/dashboard/src/components/ui/checkbox-tree.test.tsx @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { CheckboxTree, toCheckboxTreeItems } from "./checkbox-tree"; diff --git a/apps/dashboard/src/data/metrics.client.test.ts b/apps/dashboard/src/data/metrics.client.test.ts index 3ba9fb785..d01aea799 100644 --- a/apps/dashboard/src/data/metrics.client.test.ts +++ b/apps/dashboard/src/data/metrics.client.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { getMonitorListMetrics, diff --git a/apps/dashboard/src/data/overview-events.client.test.ts b/apps/dashboard/src/data/overview-events.client.test.ts index 6137ca3fc..0d743d58b 100644 --- a/apps/dashboard/src/data/overview-events.client.test.ts +++ b/apps/dashboard/src/data/overview-events.client.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { buildOverviewData, diff --git a/apps/dashboard/src/data/page-components.client.test.ts b/apps/dashboard/src/data/page-components.client.test.ts index 742ecc9f2..0724d31cf 100644 --- a/apps/dashboard/src/data/page-components.client.test.ts +++ b/apps/dashboard/src/data/page-components.client.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { getActions, diff --git a/apps/dashboard/src/data/status-codes.test.ts b/apps/dashboard/src/data/status-codes.test.ts index 6b0e5d101..54e387e0b 100644 --- a/apps/dashboard/src/data/status-codes.test.ts +++ b/apps/dashboard/src/data/status-codes.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { getStatusCodeVariant } from "./status-codes"; diff --git a/apps/dashboard/src/data/status-page-history.test.ts b/apps/dashboard/src/data/status-page-history.test.ts index 9cb61f0b1..00556a1cc 100644 --- a/apps/dashboard/src/data/status-page-history.test.ts +++ b/apps/dashboard/src/data/status-page-history.test.ts @@ -1,6 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; -import { FakeTime } from "@std/testing/time"; +import { describe, expect, it, vi } from "vitest"; import { type HistoryEvent, @@ -70,13 +68,13 @@ describe("eventsForMonth", () => { }); it("treats open-ended events as lasting until now", () => { - const time = new FakeTime(new Date("2025-05-15T00:00:00Z")); + vi.useFakeTimers({ now: new Date("2025-05-15T00:00:00Z") }); try { const open = event("2025-03-20T00:00:00Z", null); expect(eventsForMonth([open], "2025-04")).toEqual([open]); expect(eventsForMonth([open], "2025-06")).toEqual([]); } finally { - time.restore(); + vi.useRealTimers(); } }); }); diff --git a/apps/dashboard/src/data/status-reports.client.test.ts b/apps/dashboard/src/data/status-reports.client.test.ts index 85dca282c..3e85c8896 100644 --- a/apps/dashboard/src/data/status-reports.client.test.ts +++ b/apps/dashboard/src/data/status-reports.client.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { incidentEndedAt } from "./managed-incidents.client"; import { diff --git a/apps/dashboard/src/lib/auth/providers.test.ts b/apps/dashboard/src/lib/auth/providers.test.ts index 734f82427..370a941f4 100644 --- a/apps/dashboard/src/lib/auth/providers.test.ts +++ b/apps/dashboard/src/lib/auth/providers.test.ts @@ -1,18 +1,23 @@ import "@/test-preload"; import { AuthError } from "@auth/core/errors"; import { redis } from "@openstatus/upstash"; -import { expect } from "@std/expect"; -import { afterEach, describe, test } from "@std/testing/bdd"; -import { type Stub, stub } from "@std/testing/mock"; +import { + afterEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { ResendProvider } from "./providers"; -let evalStub: Stub | undefined; -let logStub: Stub | undefined; +let evalStub: MockInstance | undefined; +let logStub: MockInstance | undefined; afterEach(() => { - evalStub?.restore(); - logStub?.restore(); + evalStub?.mockRestore(); + logStub?.mockRestore(); }); function sendTo(identifier: string, ip = "1.2.3.4") { @@ -34,17 +39,21 @@ function sendTo(identifier: string, ip = "1.2.3.4") { describe("ResendProvider.sendVerificationRequest", () => { test("refuses disposable domains before touching the rate limit", async () => { - evalStub = stub(redis, "eval"); + evalStub = vi + .spyOn(redis, "eval") + .mockImplementation(() => undefined as never); await expect(sendTo("a@mailinator.com")).rejects.toThrow(AuthError); await expect(sendTo("a@mailinator.com")).rejects.toThrow( "disposable domain", ); - expect(evalStub.calls).toHaveLength(0); + expect(evalStub.mock.calls).toHaveLength(0); }); test("refuses throttled addresses", async () => { - evalStub = stub(redis, "eval", () => Promise.resolve([1, 4])); + evalStub = vi + .spyOn(redis, "eval") + .mockImplementation(() => Promise.resolve([1, 4])); await expect(sendTo("gilfoyle@piedpiper.dev")).rejects.toThrow( "rate limited", @@ -52,16 +61,20 @@ describe("ResendProvider.sendVerificationRequest", () => { }); test("sends when screening passes, keyed by client ip and address", async () => { - evalStub = stub(redis, "eval", () => Promise.resolve([1, 1])); - logStub = stub(console, "log"); + evalStub = vi + .spyOn(redis, "eval") + .mockImplementation(() => Promise.resolve([1, 1])); + logStub = vi + .spyOn(console, "log") + .mockImplementation(() => undefined as never); await sendTo("gilfoyle@piedpiper.dev", "9.9.9.9"); - expect(evalStub.calls[0]?.args[1]).toEqual([ + expect(evalStub.mock.calls[0]?.[1]).toEqual([ "ratelimit:magic-link:ip:9.9.9.9", "ratelimit:magic-link:email:gilfoyle@piedpiper.dev", ]); - const printed = logStub.calls.map((c) => String(c.args[0])).join("\n"); + const printed = logStub.mock.calls.map((c) => String(c[0])).join("\n"); expect(printed).toContain( "https://app.openstatus.dev/api/auth/callback/resend?token=t", ); diff --git a/apps/dashboard/src/lib/composition.test.ts b/apps/dashboard/src/lib/composition.test.ts index 1f21141e1..2e7cf8146 100644 --- a/apps/dashboard/src/lib/composition.test.ts +++ b/apps/dashboard/src/lib/composition.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { composeEventHandlers, composeRefs } from "./composition"; diff --git a/apps/dashboard/src/lib/domains.test.ts b/apps/dashboard/src/lib/domains.test.ts index e24e0d3a9..3023180b4 100644 --- a/apps/dashboard/src/lib/domains.test.ts +++ b/apps/dashboard/src/lib/domains.test.ts @@ -1,14 +1,22 @@ -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, it } from "@std/testing/bdd"; -import { type Stub, stub } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, + type MockInstance, +} from "vitest"; import { extractDomain, getApexDomain, getSubdomain } from "./domains"; -let errorStub: Stub | undefined; +let errorStub: MockInstance | undefined; beforeEach(() => { - errorStub = stub(console, "error"); + errorStub = vi + .spyOn(console, "error") + .mockImplementation(() => undefined as never); }); -afterEach(() => errorStub?.restore()); +afterEach(() => errorStub?.mockRestore()); describe("getSubdomain", () => { it("returns null for the apex itself", () => { diff --git a/apps/dashboard/src/lib/formatter.test.ts b/apps/dashboard/src/lib/formatter.test.ts index 9612f3d97..4255fd71c 100644 --- a/apps/dashboard/src/lib/formatter.test.ts +++ b/apps/dashboard/src/lib/formatter.test.ts @@ -1,6 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; -import { FakeTime } from "@std/testing/time"; +import { describe, expect, it, vi } from "vitest"; import { formatDateForInput, @@ -95,24 +93,24 @@ describe("formatDistanceToNowShort", () => { [10 * 24 * 60 * 60, "10d ago"], ] as const) { it(`${secondsAgo}s -> ${expected}`, () => { - const time = new FakeTime(now); + vi.useFakeTimers({ now }); try { const date = new Date(now.getTime() - secondsAgo * 1000); expect(formatDistanceToNowShort(date)).toBe(expected); } finally { - time.restore(); + vi.useRealTimers(); } }); } it("clamps future dates to 0s", () => { - const time = new FakeTime(now); + vi.useFakeTimers({ now }); try { expect(formatDistanceToNowShort(new Date(now.getTime() + 60_000))).toBe( "0s ago", ); } finally { - time.restore(); + vi.useRealTimers(); } }); }); diff --git a/apps/dashboard/src/lib/onboarding/email-domain.test.ts b/apps/dashboard/src/lib/onboarding/email-domain.test.ts index 34112fc3d..e3f700146 100644 --- a/apps/dashboard/src/lib/onboarding/email-domain.test.ts +++ b/apps/dashboard/src/lib/onboarding/email-domain.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { getCompanyDomainFromEmail } from "./email-domain"; diff --git a/apps/dashboard/src/lib/rate-limit/magic-link.test.ts b/apps/dashboard/src/lib/rate-limit/magic-link.test.ts index 966e59599..383f519ec 100644 --- a/apps/dashboard/src/lib/rate-limit/magic-link.test.ts +++ b/apps/dashboard/src/lib/rate-limit/magic-link.test.ts @@ -1,23 +1,32 @@ import "@/test-preload"; import { redis } from "@openstatus/upstash"; -import { expect } from "@std/expect"; -import { afterEach, describe, test } from "@std/testing/bdd"; -import { type Stub, stub } from "@std/testing/mock"; +import { + afterEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { magicLinkRateLimit } from "./magic-link"; -let evalStub: Stub | undefined; -let warnStub: Stub | undefined; +let evalStub: MockInstance | undefined; +let warnStub: MockInstance | undefined; function stubCounts(counts: number[] | Error) { - evalStub = stub(redis, "eval", () => - counts instanceof Error ? Promise.reject(counts) : Promise.resolve(counts), - ); + evalStub = vi + .spyOn(redis, "eval") + .mockImplementation(() => + counts instanceof Error + ? Promise.reject(counts) + : Promise.resolve(counts), + ); } afterEach(() => { - evalStub?.restore(); - warnStub?.restore(); + evalStub?.mockRestore(); + warnStub?.mockRestore(); }); describe("magicLinkRateLimit", () => { @@ -26,11 +35,11 @@ describe("magicLinkRateLimit", () => { await magicLinkRateLimit({ ip: "1.2.3.4", email: "a@b.c" }); - expect(evalStub?.calls[0]?.args[1]).toEqual([ + expect(evalStub?.mock.calls[0]?.[1]).toEqual([ "ratelimit:magic-link:ip:1.2.3.4", "ratelimit:magic-link:email:a@b.c", ]); - expect(evalStub?.calls[0]?.args[2]).toEqual([600]); + expect(evalStub?.mock.calls[0]?.[2]).toEqual([600]); }); test("lowercases the email key", async () => { @@ -38,7 +47,7 @@ describe("magicLinkRateLimit", () => { await magicLinkRateLimit({ ip: "ip", email: " Gilfoyle@PiedPiper.dev " }); - expect(evalStub?.calls[0]?.args[1]).toEqual([ + expect(evalStub?.mock.calls[0]?.[1]).toEqual([ "ratelimit:magic-link:ip:ip", "ratelimit:magic-link:email:gilfoyle@piedpiper.dev", ]); @@ -61,9 +70,11 @@ describe("magicLinkRateLimit", () => { test("fails open with a warning when redis is down", async () => { stubCounts(new Error("redis down")); - warnStub = stub(console, "warn"); + warnStub = vi + .spyOn(console, "warn") + .mockImplementation(() => undefined as never); expect(await magicLinkRateLimit({ ip: "ip", email: "e" })).toBe(true); - expect(warnStub.calls).toHaveLength(1); + expect(warnStub.mock.calls).toHaveLength(1); }); }); diff --git a/apps/dashboard/src/test/next-link.mock.tsx b/apps/dashboard/src/test/next-link.mock.tsx index 03dcc1836..95afbc9b2 100644 --- a/apps/dashboard/src/test/next-link.mock.tsx +++ b/apps/dashboard/src/test/next-link.mock.tsx @@ -1,5 +1,5 @@ -// Test double for next/link, swapped in via --import-map: Deno cannot resolve -// Next's extensionless CJS entrypoints. Renders a plain anchor. +// Test double for next/link, swapped in via test.importmap.json. Renders a +// plain anchor. import type * as React from "react"; type LinkProps = Omit, "href"> & { diff --git a/apps/dashboard/vitest.config.ts b/apps/dashboard/vitest.config.ts new file mode 100644 index 000000000..2debbded5 --- /dev/null +++ b/apps/dashboard/vitest.config.ts @@ -0,0 +1,6 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname, { + importMap: "test.importmap.json", + include: ["src/**/*.test.{ts,tsx}"], +}); diff --git a/apps/screenshot-service/tsconfig.json b/apps/screenshot-service/tsconfig.json index d1ea02542..0770bb760 100644 --- a/apps/screenshot-service/tsconfig.json +++ b/apps/screenshot-service/tsconfig.json @@ -1,14 +1,5 @@ { "extends": "@openstatus/tsconfig/base.json", "include": ["src", "*.ts", "**/*.ts"], - "compilerOptions": {}, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + "compilerOptions": {} } diff --git a/apps/server/AGENTS.md b/apps/server/AGENTS.md index 0a296c44f..3b671cad4 100644 --- a/apps/server/AGENTS.md +++ b/apps/server/AGENTS.md @@ -25,9 +25,8 @@ the handlers that have migrated, and that list grows one domain per PR. Resolve config once and pass it in; `createSlackRoute(config)` in `apps/server/src/routes/slack/index.ts` is the pattern — production calls `slackConfigFromEnv()` at module scope, tests build a -route with explicit config. Reading `env` at request time is what made the slack -route untestable under `deno test --parallel`, since the workers share one -process environment. +route with explicit config. Reading `env` at request time would force tests to +mutate the shared `process.env`, which leaks between cases. ## MCP diff --git a/apps/server/package.json b/apps/server/package.json index 77957cc3e..b197d4d54 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -8,13 +8,13 @@ "env": "node env.ts", "dev": "node --import @oxc-node/core/register --watch --env-file=.env src/serve.ts", "start": "NODE_ENV=production node dist/serve.mjs", - "test": "deno test --parallel -A --no-check --sloppy-imports --env-file=.env.test --import-map=test.importmap.json", + "test": "vitest run", "check": "tsc --noEmit", "slack:manifest:prod": "cat slack-manifest.json", "slack:manifest:dev": "node --import @oxc-node/core/register --env-file=.env scripts/slack-manifest-dev.ts", "eval:mcp": "node --import @oxc-node/core/register src/routes/mcp/evals/run.ts", "openapi:json": "node --import @oxc-node/core/register scripts/build-openapi-json.ts && pnpm exec oxfmt static/openapi.json static/openapi-yaml.ts", - "openapi:v1": "deno run -A --no-check --sloppy-imports --env-file=.env.test --import-map=test.importmap.json scripts/build-openapi-v1-json.ts && pnpm exec oxfmt static/openapi-v1.json", + "openapi:v1": "node --import @oxc-node/core/register --env-file=.env.test scripts/build-openapi-v1-json.ts && pnpm exec oxfmt static/openapi-v1.json", "build": "tsdown" }, "dependencies": { @@ -87,12 +87,11 @@ "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", "@oxc-node/core": "catalog:", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@std/yaml": "jsr:^1.0.9", "@types/validator": "catalog:", "dotenv": "catalog:", "tsdown": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/apps/server/src/libs/checker/utils.test.ts b/apps/server/src/libs/checker/utils.test.ts index c1417407d..683b66191 100644 --- a/apps/server/src/libs/checker/utils.test.ts +++ b/apps/server/src/libs/checker/utils.test.ts @@ -1,6 +1,6 @@ import type { z } from "@hono/zod-openapi"; import type { selectMonitorSchema } from "@openstatus/db/src/schema"; -import { expect, test } from "@openstatus/test-utils"; +import { expect, test } from "vitest"; import { OpenStatusApiError } from "@/libs/errors"; diff --git a/apps/server/src/libs/cli-telemetry.test.ts b/apps/server/src/libs/cli-telemetry.test.ts index e072dedb2..913ba365d 100644 --- a/apps/server/src/libs/cli-telemetry.test.ts +++ b/apps/server/src/libs/cli-telemetry.test.ts @@ -1,6 +1,5 @@ import { Events } from "@openstatus/analytics"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { claimCliCommandEvent, diff --git a/apps/server/src/libs/middlewares/concurrency.test.ts b/apps/server/src/libs/middlewares/concurrency.test.ts index 32b837bfc..4db594f9d 100644 --- a/apps/server/src/libs/middlewares/concurrency.test.ts +++ b/apps/server/src/libs/middlewares/concurrency.test.ts @@ -1,7 +1,6 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; import { Hono } from "hono"; import { requestId } from "hono/request-id"; +import { describe, expect, test } from "vitest"; import { createConcurrencyGuard } from "./concurrency"; diff --git a/apps/server/src/libs/middlewares/credentials.test.ts b/apps/server/src/libs/middlewares/credentials.test.ts index 4acffc5f1..738114d79 100644 --- a/apps/server/src/libs/middlewares/credentials.test.ts +++ b/apps/server/src/libs/middlewares/credentials.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { extractCredential } from "./credentials"; diff --git a/apps/server/src/libs/middlewares/rate-limit.test.ts b/apps/server/src/libs/middlewares/rate-limit.test.ts index 860efe3b1..d7ed0d232 100644 --- a/apps/server/src/libs/middlewares/rate-limit.test.ts +++ b/apps/server/src/libs/middlewares/rate-limit.test.ts @@ -1,8 +1,6 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; -import { FakeTime } from "@std/testing/time"; import { type Context, Hono } from "hono"; import { requestId } from "hono/request-id"; +import { describe, expect, test, vi } from "vitest"; import { createRateLimit, credentialKey } from "./rate-limit"; @@ -227,23 +225,23 @@ describe("rate limit", () => { test("the 10 s bucket recovers after its window while the minute window keeps counting", async () => { const { app } = recovery; - const time = new FakeTime(); + vi.useFakeTimers(); try { const init = { headers: keyHeaders("a") }; expect((await app.request("/v1/monitor", init)).status).toBe(200); expect((await app.request("/v1/monitor", init)).status).toBe(429); - time.tick(10_001); + vi.advanceTimersByTime(10_001); expect((await app.request("/v1/monitor", init)).status).toBe(200); // minute window (limit 2) is now full even though the burst bucket reset - time.tick(10_001); + vi.advanceTimersByTime(10_001); expect((await app.request("/v1/monitor", init)).status).toBe(429); - time.tick(60_001); + vi.advanceTimersByTime(60_001); expect((await app.request("/v1/monitor", init)).status).toBe(200); } finally { - time.restore(); + vi.useRealTimers(); } }); diff --git a/apps/server/src/libs/middlewares/require-scope.test.ts b/apps/server/src/libs/middlewares/require-scope.test.ts index f1f0962a6..2de3e8731 100644 --- a/apps/server/src/libs/middlewares/require-scope.test.ts +++ b/apps/server/src/libs/middlewares/require-scope.test.ts @@ -1,7 +1,6 @@ import type { Scope } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; import { Hono } from "hono"; +import { describe, expect, test } from "vitest"; import type { Variables } from "@/types"; diff --git a/apps/server/src/libs/middlewares/track.test.ts b/apps/server/src/libs/middlewares/track.test.ts index bf21cb126..8af196a25 100644 --- a/apps/server/src/libs/middlewares/track.test.ts +++ b/apps/server/src/libs/middlewares/track.test.ts @@ -1,13 +1,7 @@ import { Events } from "@openstatus/analytics"; import type { Workspace } from "@openstatus/db/src/schema"; -import { - beforeEach, - describe, - expect, - type mock, - test, -} from "@openstatus/test-utils"; import { Hono } from "hono"; +import { beforeEach, describe, expect, test, vi, type Mock } from "vitest"; import type { Variables } from "@/types"; @@ -18,8 +12,8 @@ import { apiTrackMiddleware } from "./track"; const { track: mockTrack, setupAnalytics: mockSetupAnalytics } = ( globalThis as Record ).__analyticsSpies as { - track: ReturnType; - setupAnalytics: ReturnType; + track: Mock; + setupAnalytics: Mock; }; /** Let the fire-and-forget analytics chain settle. */ diff --git a/apps/server/src/libs/middlewares/validate-key.test.ts b/apps/server/src/libs/middlewares/validate-key.test.ts index f4ecabba5..524facecd 100644 --- a/apps/server/src/libs/middlewares/validate-key.test.ts +++ b/apps/server/src/libs/middlewares/validate-key.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { validateKey } from "./auth"; diff --git a/apps/server/src/libs/sampling.test.ts b/apps/server/src/libs/sampling.test.ts index 0983df614..986f8739d 100644 --- a/apps/server/src/libs/sampling.test.ts +++ b/apps/server/src/libs/sampling.test.ts @@ -1,15 +1,13 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; -import { stub } from "@std/testing/mock"; +import { describe, expect, test, vi } from "vitest"; import { shouldSample } from "./sampling"; function withRandom(value: number, fn: () => T): T { - const random = stub(Math, "random", () => value); + const random = vi.spyOn(Math, "random").mockImplementation(() => value); try { return fn(); } finally { - random.restore(); + random.mockRestore(); } } diff --git a/apps/server/src/libs/test/doubles/analytics.mock.ts b/apps/server/src/libs/test/doubles/analytics.mock.ts index 3ac496513..ec830b0a6 100644 --- a/apps/server/src/libs/test/doubles/analytics.mock.ts +++ b/apps/server/src/libs/test/doubles/analytics.mock.ts @@ -9,20 +9,20 @@ export { parseInputToProps, } from "@openstatus/analytics-real"; -import { mock } from "@openstatus/test-utils"; +import { vi, type Mock } from "vitest"; const g = globalThis as Record; if (!g.__analyticsSpies) { - const track = mock(() => Promise.resolve()); + const track = vi.fn(() => Promise.resolve()); g.__analyticsSpies = { track, - setupAnalytics: mock((_props: unknown) => Promise.resolve({ track })), + setupAnalytics: vi.fn((_props: unknown) => Promise.resolve({ track })), }; } const spies = g.__analyticsSpies as { - track: ReturnType; - setupAnalytics: ReturnType; + track: Mock; + setupAnalytics: Mock; }; export const setupAnalytics = (props: unknown) => spies.setupAnalytics(props); diff --git a/apps/server/src/libs/test/doubles/state.ts b/apps/server/src/libs/test/doubles/state.ts index ec2239076..6b2279010 100644 --- a/apps/server/src/libs/test/doubles/state.ts +++ b/apps/server/src/libs/test/doubles/state.ts @@ -1,22 +1,22 @@ // Shared test-double state, set on globalThis so tests can assert on spies and // configure mock data — same interface the old bun `mock.module` preload // exposed. Imported (for side effect) by every double module below. -import { mock } from "@openstatus/test-utils"; +import { vi, type Mock } from "vitest"; type Mut = Record; const g = globalThis as Mut; if (!g.__subscriptionSpies) { - const sendVerification = mock(() => Promise.resolve()); + const sendVerification = vi.fn(() => Promise.resolve()); g.__subscriptionSpies = { - dispatchStatusReportUpdate: mock(() => Promise.resolve()), - dispatchMaintenanceUpdate: mock(() => Promise.resolve()), + dispatchStatusReportUpdate: vi.fn(() => Promise.resolve()), + dispatchMaintenanceUpdate: vi.fn(() => Promise.resolve()), sendVerification, getChannel: { id: "email", sendVerification, - sendNotifications: mock(() => Promise.resolve()), - validateConfig: mock(() => Promise.resolve({ valid: true })), + sendNotifications: vi.fn(() => Promise.resolve()), + validateConfig: vi.fn(() => Promise.resolve({ valid: true })), }, }; } @@ -33,9 +33,9 @@ if (!g.__tinybirdMockCalls) { } export const subscriptionSpies = g.__subscriptionSpies as { - dispatchStatusReportUpdate: ReturnType; - dispatchMaintenanceUpdate: ReturnType; - sendVerification: ReturnType; + dispatchStatusReportUpdate: Mock; + dispatchMaintenanceUpdate: Mock; + sendVerification: Mock; getChannel: Record; }; export const testRedisStore = g.__testRedisStore as Map; diff --git a/apps/server/src/libs/test/preload.ts b/apps/server/src/libs/test/preload.ts deleted file mode 100644 index 232bf7f9f..000000000 --- a/apps/server/src/libs/test/preload.ts +++ /dev/null @@ -1,196 +0,0 @@ -// Import the real module BEFORE mock.module runs so we can spread real -// service functions (createSubscription, updateChannel, etc.) into the -// replacement while still stubbing the dispatch-side spies. -import * as realSubscriptions from "@openstatus/subscriptions"; -import { mock } from "@openstatus/test-utils"; - -// Subscription dispatch spies — accessible in tests via globalThis.__subscriptionSpies -const dispatchStatusReportUpdateSpy = mock((_id: number) => Promise.resolve()); -const dispatchMaintenanceUpdateSpy = mock((_id: number) => Promise.resolve()); - -const sendVerificationSpy = mock( - (_subscription: realSubscriptions.Subscription, _verifyUrl: string) => - Promise.resolve(), -); - -const getChannelMock = { - id: "email", - sendVerification: sendVerificationSpy, - sendNotifications: mock(() => Promise.resolve()), - validateConfig: mock(() => Promise.resolve({ valid: true })), -}; - -(globalThis as Record).__subscriptionSpies = { - dispatchStatusReportUpdate: dispatchStatusReportUpdateSpy, - dispatchMaintenanceUpdate: dispatchMaintenanceUpdateSpy, - sendVerification: sendVerificationSpy, - getChannel: getChannelMock, -}; - -mock.module("@openstatus/subscriptions", () => ({ - ...realSubscriptions, - dispatchStatusReportUpdate: dispatchStatusReportUpdateSpy, - dispatchMaintenanceUpdate: dispatchMaintenanceUpdateSpy, - getChannel: () => getChannelMock, -})); - -const testRedisStore = new Map(); -(globalThis as Record).__testRedisStore = testRedisStore; - -mock.module("@openstatus/upstash", () => ({ - Redis: { - fromEnv() { - return { - get: (key: string) => Promise.resolve(testRedisStore.get(key) ?? null), - set: (key: string, value: string) => { - testRedisStore.set(key, value); - return Promise.resolve("OK"); - }, - del: (key: string) => { - const existed = testRedisStore.has(key) ? 1 : 0; - testRedisStore.delete(key); - return Promise.resolve(existed); - }, - getdel: (key: string) => { - const value = testRedisStore.get(key) ?? null; - testRedisStore.delete(key); - return Promise.resolve(value); - }, - expire: (_key: string, _seconds: number) => { - return Promise.resolve(1); - }, - }; - }, - }, -})); - -const tinybirdMockData = { - httpListBiweekly: [] as unknown[], - httpGetBiweekly: [] as unknown[], -}; -const tinybirdMockCalls = { - httpListBiweekly: [] as unknown[], - httpGetBiweekly: [] as unknown[], -}; - -(globalThis as Record).__tinybirdMockData = tinybirdMockData; -(globalThis as Record).__tinybirdMockCalls = tinybirdMockCalls; - -const emptyTinybirdResponse = () => Promise.resolve({ data: [] }); - -const tbMock = { - get legacy_httpStatus45d() { - return emptyTinybirdResponse; - }, - get legacy_tcpStatus45d() { - return emptyTinybirdResponse; - }, - get httpListBiweekly() { - return (params: unknown) => { - tinybirdMockCalls.httpListBiweekly.push(params); - return Promise.resolve({ data: tinybirdMockData.httpListBiweekly }); - }; - }, - get httpGetBiweekly() { - return (params: unknown) => { - tinybirdMockCalls.httpGetBiweekly.push(params); - return Promise.resolve({ data: tinybirdMockData.httpGetBiweekly }); - }; - }, - get httpMetricsDaily() { - return emptyTinybirdResponse; - }, - get httpMetricsWeekly() { - return emptyTinybirdResponse; - }, - get httpMetricsBiweekly() { - return emptyTinybirdResponse; - }, - get tcpMetricsDaily() { - return emptyTinybirdResponse; - }, - get tcpMetricsWeekly() { - return emptyTinybirdResponse; - }, - get tcpMetricsBiweekly() { - return emptyTinybirdResponse; - }, - get dnsMetricsDaily() { - return emptyTinybirdResponse; - }, - get dnsMetricsWeekly() { - return emptyTinybirdResponse; - }, - get dnsMetricsBiweekly() { - return emptyTinybirdResponse; - }, -}; - -mock.module("@/libs/clients", () => ({ - tb: tbMock, - redis: { - get: (key: string) => Promise.resolve(testRedisStore.get(key) ?? null), - set: (key: string, value: string) => { - testRedisStore.set(key, value); - return Promise.resolve("OK"); - }, - del: (key: string) => { - const existed = testRedisStore.has(key) ? 1 : 0; - testRedisStore.delete(key); - return Promise.resolve(existed); - }, - getdel: (key: string) => { - const value = testRedisStore.get(key) ?? null; - testRedisStore.delete(key); - return Promise.resolve(value); - }, - expire: (_key: string, _seconds: number) => Promise.resolve(1), - }, -})); - -mock.module("@openstatus/tinybird", () => ({ - OSTinybird: class { - get legacy_httpStatus45d() { - return tbMock.legacy_httpStatus45d; - } - get legacy_tcpStatus45d() { - return tbMock.legacy_tcpStatus45d; - } - get httpListBiweekly() { - return tbMock.httpListBiweekly; - } - get httpGetBiweekly() { - return tbMock.httpGetBiweekly; - } - // HTTP metrics for GetMonitorSummary - get httpMetricsDaily() { - return tbMock.httpMetricsDaily; - } - get httpMetricsWeekly() { - return tbMock.httpMetricsWeekly; - } - get httpMetricsBiweekly() { - return tbMock.httpMetricsBiweekly; - } - // TCP metrics for GetMonitorSummary - get tcpMetricsDaily() { - return tbMock.tcpMetricsDaily; - } - get tcpMetricsWeekly() { - return tbMock.tcpMetricsWeekly; - } - get tcpMetricsBiweekly() { - return tbMock.tcpMetricsBiweekly; - } - // DNS metrics for GetMonitorSummary - get dnsMetricsDaily() { - return tbMock.dnsMetricsDaily; - } - get dnsMetricsWeekly() { - return tbMock.dnsMetricsWeekly; - } - get dnsMetricsBiweekly() { - return tbMock.dnsMetricsBiweekly; - } - }, -})); diff --git a/apps/server/src/overload.test.ts b/apps/server/src/overload.test.ts index e0609f27d..ba6d1375a 100644 --- a/apps/server/src/overload.test.ts +++ b/apps/server/src/overload.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { app } from "@/index"; import { concurrencyGuard } from "@/libs/middlewares/concurrency"; @@ -8,7 +7,7 @@ import { limits } from "@/libs/middlewares/limits"; /** * Wiring test: the guards must be mounted in the real app, after the wide * event and before every route. Limits are lowered on the in-isolate - * `limits` object, never via `Deno.env`, which is shared by parallel workers. + * `limits` object, never via `process.env`, which every test in the file shares. */ const original = { ...limits }; const tick = () => new Promise((r) => setTimeout(r, 0)); diff --git a/apps/server/src/routes/mcp/adapter.test.ts b/apps/server/src/routes/mcp/adapter.test.ts index b1a00d1a1..ca4af4473 100644 --- a/apps/server/src/routes/mcp/adapter.test.ts +++ b/apps/server/src/routes/mcp/adapter.test.ts @@ -1,7 +1,6 @@ import type { Workspace } from "@openstatus/db/src/schema"; import { ForbiddenError, NotFoundError } from "@openstatus/services"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { mapError, toServiceCtx } from "./adapter"; diff --git a/apps/server/src/routes/mcp/handler.test.ts b/apps/server/src/routes/mcp/handler.test.ts index d843a3576..a78e1fe9d 100644 --- a/apps/server/src/routes/mcp/handler.test.ts +++ b/apps/server/src/routes/mcp/handler.test.ts @@ -4,17 +4,17 @@ import { db, desc, eq } from "@openstatus/db"; import { auditLog, page, statusReport } from "@openstatus/db/src/schema"; import { resourceMetadataUrl } from "@openstatus/services/oauth"; import { SEEDED_WORKSPACE_TEAM_ID } from "@openstatus/services/test/fixtures"; -import type { MockFn } from "@openstatus/test-utils"; -import { expect } from "@std/expect"; +import { Hono } from "hono"; +import { requestId } from "hono/request-id"; import { afterAll, beforeAll, beforeEach, describe, + expect, test, -} from "@std/testing/bdd"; -import { Hono } from "hono"; -import { requestId } from "hono/request-id"; + type Mock, +} from "vitest"; import { handleError } from "../../libs/errors"; import { oauthConfigFromEnv } from "../oauth/config"; @@ -253,7 +253,7 @@ describe("MCP transport", () => { * spies live on `globalThis.__analyticsSpies`. */ const analyticsSpies = (globalThis as Record) - .__analyticsSpies as { track: MockFn; setupAnalytics: MockFn }; + .__analyticsSpies as { track: Mock; setupAnalytics: Mock }; /** * Tracking is fire-and-forget — `track` runs in a microtask chained off diff --git a/apps/server/src/routes/mcp/resources.test.ts b/apps/server/src/routes/mcp/resources.test.ts index 0ab60331b..71339e1e9 100644 --- a/apps/server/src/routes/mcp/resources.test.ts +++ b/apps/server/src/routes/mcp/resources.test.ts @@ -1,8 +1,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; import type { Workspace } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterEach, describe, test } from "@std/testing/bdd"; +import { afterEach, describe, expect, test } from "vitest"; import { toServiceCtx } from "./adapter"; import { PUBLIC_RESOURCES, readPublicResource } from "./resources"; @@ -45,7 +44,9 @@ describe("public MCP resources", () => { stubFetch(() => Promise.reject(new Error("network down"))); for (const resource of PUBLIC_RESOURCES) { const result = await readPublicResource(resource); - expect(String(result.contents[0].text).length).toBeGreaterThan(0); + expect( + String((result.contents[0] as { text?: unknown }).text).length, + ).toBeGreaterThan(0); } }); @@ -55,13 +56,17 @@ describe("public MCP resources", () => { stubFetch(() => Promise.resolve(new Response(" "))); const blank = await readPublicResource(remote); - expect(String(blank.contents[0].text).trim().length).toBeGreaterThan(0); + expect( + String((blank.contents[0] as { text?: unknown }).text).trim().length, + ).toBeGreaterThan(0); stubFetch(() => Promise.resolve(new Response("not found", { status: 404 })), ); const missing = await readPublicResource(remote); - expect(String(missing.contents[0].text).trim().length).toBeGreaterThan(0); + expect( + String((missing.contents[0] as { text?: unknown }).text).trim().length, + ).toBeGreaterThan(0); }); test("serves the fetched document when it resolves", async () => { @@ -72,7 +77,9 @@ describe("public MCP resources", () => { stubFetch(() => Promise.resolve(new Response("# MCP server\n\nlive copy"))); const result = await readPublicResource(remote); - expect(result.contents[0].text).toContain("live copy"); + expect((result.contents[0] as { text?: unknown }).text).toContain( + "live copy", + ); }); test("ships the OpenAPI document without a network call", async () => { @@ -83,7 +90,9 @@ describe("public MCP resources", () => { if (!spec) throw new Error("openapi resource missing"); const result = await readPublicResource(spec); - const parsed = JSON.parse(String(result.contents[0].text)); + const parsed = JSON.parse( + String((result.contents[0] as { text?: unknown }).text), + ); expect(parsed.openapi).toBe("3.1.0"); expect(parsed.servers[0].url).toBe("https://api.openstatus.dev"); }); @@ -155,7 +164,9 @@ describe("public MCP documents over the transport", () => { try { for (const resource of PUBLIC_RESOURCES) { const result = await client.readResource({ uri: resource.uri }); - expect(String(result.contents[0].text).length).toBeGreaterThan(0); + expect( + String((result.contents[0] as { text?: unknown }).text).length, + ).toBeGreaterThan(0); } } finally { await close(); diff --git a/apps/server/src/routes/mcp/tools/register-scoped.test.ts b/apps/server/src/routes/mcp/tools/register-scoped.test.ts index 3b5eae6f7..141772f1a 100644 --- a/apps/server/src/routes/mcp/tools/register-scoped.test.ts +++ b/apps/server/src/routes/mcp/tools/register-scoped.test.ts @@ -1,8 +1,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import type { Workspace } from "@openstatus/db/src/schema"; import type { ServiceContext } from "@openstatus/services"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { registerScopedTool } from "./register-scoped"; diff --git a/apps/server/src/routes/mcp/tools/tools.test.ts b/apps/server/src/routes/mcp/tools/tools.test.ts index 6755d1874..d0f591c5a 100644 --- a/apps/server/src/routes/mcp/tools/tools.test.ts +++ b/apps/server/src/routes/mcp/tools/tools.test.ts @@ -17,8 +17,7 @@ import { readAuditLog, withTestTransaction, } from "@openstatus/services/test/helpers"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { toServiceCtx } from "../adapter"; import { registerMaintenanceTools } from "./maintenance"; diff --git a/apps/server/src/routes/oauth/oauth.test.ts b/apps/server/src/routes/oauth/oauth.test.ts index c86b5ef83..aaf90e611 100644 --- a/apps/server/src/routes/oauth/oauth.test.ts +++ b/apps/server/src/routes/oauth/oauth.test.ts @@ -14,9 +14,8 @@ import { pkceChallenge, } from "@openstatus/services/oauth"; import { clearAuditLogFor } from "@openstatus/services/test/helpers"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; import { Hono } from "hono"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { app } from "../../index"; import { oauthConfigFromEnv } from "./config"; diff --git a/apps/server/src/routes/openapi.test.ts b/apps/server/src/routes/openapi.test.ts index 666884f2f..2c007f68f 100644 --- a/apps/server/src/routes/openapi.test.ts +++ b/apps/server/src/routes/openapi.test.ts @@ -1,7 +1,8 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { readFile } from "node:fs/promises"; + +import { parse as parseYaml } from "@std/yaml"; import { Hono } from "hono"; -import { parse as parseYaml } from "jsr:@std/yaml@^1.0.9"; +import { describe, expect, test } from "vitest"; import openapiV1Json from "../../static/openapi-v1.json" with { type: "json" }; import openapiYaml from "../../static/openapi-yaml"; @@ -32,8 +33,9 @@ function withoutDateDefaults(value: unknown): unknown { describe("OpenAPI documents", () => { test("openapi.json is a faithful rendering of openapi.yaml", async () => { const yaml = parseYaml( - await Deno.readTextFile( + await readFile( new URL("../../static/openapi.yaml", import.meta.url), + "utf8", ), ); expect(JSON.parse(JSON.stringify(openapiJson))).toEqual(yaml); @@ -41,8 +43,9 @@ describe("OpenAPI documents", () => { test("the embedded YAML module matches openapi.yaml on disk", async () => { expect(openapiYaml).toBe( - await Deno.readTextFile( + await readFile( new URL("../../static/openapi.yaml", import.meta.url), + "utf8", ), ); }); diff --git a/apps/server/src/routes/public/status.test.ts b/apps/server/src/routes/public/status.test.ts index 0945504b0..1e91b9f2e 100644 --- a/apps/server/src/routes/public/status.test.ts +++ b/apps/server/src/routes/public/status.test.ts @@ -1,12 +1,12 @@ import { db, eq } from "@openstatus/db"; -import { expect } from "@std/expect"; import { afterAll, beforeAll, beforeEach, describe, + expect, test, -} from "@std/testing/bdd"; +} from "vitest"; import { app } from "../../index"; diff --git a/apps/server/src/routes/rpc/adapter.test.ts b/apps/server/src/routes/rpc/adapter.test.ts index 72e7e1347..e1c8b93b0 100644 --- a/apps/server/src/routes/rpc/adapter.test.ts +++ b/apps/server/src/routes/rpc/adapter.test.ts @@ -11,8 +11,7 @@ import { createWorkspaceFixture, withTestTransaction, } from "@openstatus/services/test/helpers"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { toConnectError, toServiceCtx } from "./adapter"; diff --git a/apps/server/src/routes/rpc/handlers/maintenance/__tests__/maintenance.test.ts b/apps/server/src/routes/rpc/handlers/maintenance/__tests__/maintenance.test.ts index ad72c9a60..86b2f1051 100644 --- a/apps/server/src/routes/rpc/handlers/maintenance/__tests__/maintenance.test.ts +++ b/apps/server/src/routes/rpc/handlers/maintenance/__tests__/maintenance.test.ts @@ -9,17 +9,23 @@ import { workspace, } from "@openstatus/db/src/schema"; import { createTestWorkspace } from "@openstatus/db/src/test/factories"; -import { mock } from "@openstatus/test-utils"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; import { nanoid } from "nanoid"; +import { + afterAll, + beforeAll, + describe, + expect, + test, + vi, + type Mock, +} from "vitest"; import { app } from "../../../../../index"; const subscriptionSpies = (globalThis as Record) .__subscriptionSpies as { - dispatchStatusReportUpdate: ReturnType; - dispatchMaintenanceUpdate: ReturnType; + dispatchStatusReportUpdate: Mock; + dispatchMaintenanceUpdate: Mock; }; /** diff --git a/apps/server/src/routes/rpc/handlers/monitor/__tests__/monitor.test.ts b/apps/server/src/routes/rpc/handlers/monitor/__tests__/monitor.test.ts index d6acd4a10..15ef02973 100644 --- a/apps/server/src/routes/rpc/handlers/monitor/__tests__/monitor.test.ts +++ b/apps/server/src/routes/rpc/handlers/monitor/__tests__/monitor.test.ts @@ -14,14 +14,14 @@ import { createMonitor, createWorkspace, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; import { afterAll, beforeAll, beforeEach, describe, + expect, test, -} from "@std/testing/bdd"; +} from "vitest"; import { app } from "../../../../../index"; @@ -3062,16 +3062,16 @@ describe("MonitorService.GetMonitor", () => { // Insert a monitor with invalid data that will fail selectMonitorSchema parsing const corruptedMon = await db .insert(monitor) - // @ts-expect-error - intentionally invalid periodicity to test parse failure .values({ workspaceId: 1, name: `${TEST_PREFIX}-corrupted-data`, url: "https://corrupted.example.com", + // intentionally invalid periodicity to test parse failure periodicity: "invalid-periodicity", active: true, regions: "ams", jobType: "http", - }) + } as unknown as typeof monitor.$inferInsert) .returning() .get(); diff --git a/apps/server/src/routes/rpc/handlers/monitor/converters/assertions.test.ts b/apps/server/src/routes/rpc/handlers/monitor/converters/assertions.test.ts index 30001928e..8e2339680 100644 --- a/apps/server/src/routes/rpc/handlers/monitor/converters/assertions.test.ts +++ b/apps/server/src/routes/rpc/handlers/monitor/converters/assertions.test.ts @@ -8,8 +8,7 @@ import { type StatusCodeAssertion, StringComparator, } from "@openstatus/proto/monitor/v1"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { z } from "zod"; import { diff --git a/apps/server/src/routes/rpc/handlers/monitor/converters/headers.test.ts b/apps/server/src/routes/rpc/handlers/monitor/converters/headers.test.ts index a0eebfb88..a329fbba6 100644 --- a/apps/server/src/routes/rpc/handlers/monitor/converters/headers.test.ts +++ b/apps/server/src/routes/rpc/handlers/monitor/converters/headers.test.ts @@ -2,8 +2,7 @@ import type { Headers, OpenTelemetryConfig, } from "@openstatus/proto/monitor/v1"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { parseOpenTelemetry, diff --git a/apps/server/src/routes/rpc/handlers/monitor/converters/regions.test.ts b/apps/server/src/routes/rpc/handlers/monitor/converters/regions.test.ts index c617b766b..0fdb6807c 100644 --- a/apps/server/src/routes/rpc/handlers/monitor/converters/regions.test.ts +++ b/apps/server/src/routes/rpc/handlers/monitor/converters/regions.test.ts @@ -1,7 +1,6 @@ import { Region } from "@openstatus/proto/monitor/v1"; import { AVAILABLE_REGIONS, RAILWAY_REGIONS } from "@openstatus/regions"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { regionToString, diff --git a/apps/server/src/routes/rpc/handlers/monitor/response-logs.test.ts b/apps/server/src/routes/rpc/handlers/monitor/response-logs.test.ts index 315eb4dfc..9720ac79c 100644 --- a/apps/server/src/routes/rpc/handlers/monitor/response-logs.test.ts +++ b/apps/server/src/routes/rpc/handlers/monitor/response-logs.test.ts @@ -1,6 +1,5 @@ import { redactSensitiveHeaders } from "@openstatus/services/monitor"; -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; test("redactSensitiveHeaders returns an empty object when headers are missing", () => { expect(redactSensitiveHeaders(null)).toEqual({}); diff --git a/apps/server/src/routes/rpc/handlers/monitor/validators.test.ts b/apps/server/src/routes/rpc/handlers/monitor/validators.test.ts index 1d3546a0e..a25af51ba 100644 --- a/apps/server/src/routes/rpc/handlers/monitor/validators.test.ts +++ b/apps/server/src/routes/rpc/handlers/monitor/validators.test.ts @@ -1,7 +1,6 @@ import { ConnectError } from "@connectrpc/connect"; import { Periodicity, Region } from "@openstatus/proto/monitor/v1"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { MONITOR_DEFAULTS } from "./converters"; import { diff --git a/apps/server/src/routes/rpc/handlers/notification/__tests__/notification.test.ts b/apps/server/src/routes/rpc/handlers/notification/__tests__/notification.test.ts index 14796f5b5..0b905f328 100644 --- a/apps/server/src/routes/rpc/handlers/notification/__tests__/notification.test.ts +++ b/apps/server/src/routes/rpc/handlers/notification/__tests__/notification.test.ts @@ -4,8 +4,7 @@ import { notificationsToMonitors, } from "@openstatus/db/src/schema"; import { createTestWorkspace } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { app } from "../../../../../index"; diff --git a/apps/server/src/routes/rpc/handlers/private-location/__tests__/private-location.test.ts b/apps/server/src/routes/rpc/handlers/private-location/__tests__/private-location.test.ts index 791c79d61..1de43b343 100644 --- a/apps/server/src/routes/rpc/handlers/private-location/__tests__/private-location.test.ts +++ b/apps/server/src/routes/rpc/handlers/private-location/__tests__/private-location.test.ts @@ -6,9 +6,8 @@ import { workspace, } from "@openstatus/db/src/schema"; import { getLimits } from "@openstatus/db/src/schema/plan/utils"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; import { nanoid } from "nanoid"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { app } from "../../../../../index"; diff --git a/apps/server/src/routes/rpc/handlers/status-page/__tests__/status-page.test.ts b/apps/server/src/routes/rpc/handlers/status-page/__tests__/status-page.test.ts index 616b72a16..592def1be 100644 --- a/apps/server/src/routes/rpc/handlers/status-page/__tests__/status-page.test.ts +++ b/apps/server/src/routes/rpc/handlers/status-page/__tests__/status-page.test.ts @@ -10,9 +10,15 @@ import { statusReportsToPageComponents, } from "@openstatus/db/src/schema"; import { createTestWorkspace } from "@openstatus/db/src/test/factories"; -import { mock } from "@openstatus/test-utils"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { + afterAll, + beforeAll, + describe, + expect, + test, + vi, + type Mock, +} from "vitest"; import { app } from "../../../../../index"; @@ -2256,7 +2262,7 @@ describe("StatusPageService.UpdateComponentGroup", () => { const subscriptionSpies = (globalThis as Record) .__subscriptionSpies as { - sendVerification: ReturnType; + sendVerification: Mock; }; describe("StatusPageService.SubscribeToPage", () => { diff --git a/apps/server/src/routes/rpc/handlers/status-report/__tests__/status-report.test.ts b/apps/server/src/routes/rpc/handlers/status-report/__tests__/status-report.test.ts index 17d66d80f..6cc9fcef2 100644 --- a/apps/server/src/routes/rpc/handlers/status-report/__tests__/status-report.test.ts +++ b/apps/server/src/routes/rpc/handlers/status-report/__tests__/status-report.test.ts @@ -15,9 +15,15 @@ import { PageComponentImpact, StatusReportStatus, } from "@openstatus/proto/status_report/v1"; -import { mock } from "@openstatus/test-utils"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { + afterAll, + beforeAll, + describe, + expect, + test, + vi, + type Mock, +} from "vitest"; import { app } from "../../../../../index"; import { @@ -28,8 +34,8 @@ import { const subscriptionSpies = (globalThis as Record) .__subscriptionSpies as { - dispatchStatusReportUpdate: ReturnType; - dispatchMaintenanceUpdate: ReturnType; + dispatchStatusReportUpdate: Mock; + dispatchMaintenanceUpdate: Mock; }; /** diff --git a/apps/server/src/routes/rpc/interceptors/__tests__/error.test.ts b/apps/server/src/routes/rpc/interceptors/__tests__/error.test.ts index 8997a44f0..1302fbf38 100644 --- a/apps/server/src/routes/rpc/interceptors/__tests__/error.test.ts +++ b/apps/server/src/routes/rpc/interceptors/__tests__/error.test.ts @@ -1,5 +1,5 @@ import { Code, ConnectError, type Interceptor } from "@connectrpc/connect"; -import { describe, expect, test } from "@openstatus/test-utils"; +import { describe, expect, test } from "vitest"; import { RPC_CONTEXT_KEY } from "../auth"; import { errorInterceptor } from "../error"; diff --git a/apps/server/src/routes/rpc/interceptors/__tests__/tracking.test.ts b/apps/server/src/routes/rpc/interceptors/__tests__/tracking.test.ts index baa056999..616e479cd 100644 --- a/apps/server/src/routes/rpc/interceptors/__tests__/tracking.test.ts +++ b/apps/server/src/routes/rpc/interceptors/__tests__/tracking.test.ts @@ -1,15 +1,9 @@ +// @ts-nocheck — ConnectRPC's deep generic types (AnyFn, UnaryResponse, etc.) +// are incompatible with the test mocks. All runtime behavior is correct. import type { Interceptor } from "@connectrpc/connect"; import { Events } from "@openstatus/analytics"; import { MonitorService } from "@openstatus/proto/monitor/v1"; -// @ts-nocheck — ConnectRPC's deep generic types (AnyFn, UnaryResponse, etc.) -// are incompatible with the test mocks. All runtime behavior is correct. -import { - beforeEach, - describe, - expect, - mock, - test, -} from "@openstatus/test-utils"; +import { beforeEach, describe, expect, test, vi, type Mock } from "vitest"; import { RPC_CONTEXT_KEY } from "../auth"; import { RPC_EVENT_MAP, trackingInterceptor } from "../tracking"; @@ -19,8 +13,8 @@ import { RPC_EVENT_MAP, trackingInterceptor } from "../tracking"; const { track: mockTrack, setupAnalytics: mockSetupAnalytics } = ( globalThis as Record ).__analyticsSpies as { - track: ReturnType; - setupAnalytics: ReturnType; + track: Mock; + setupAnalytics: Mock; }; type NextFn = Parameters>[0]; @@ -30,12 +24,12 @@ const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); /** Create a mock `next` that resolves with the given value. */ function mockNext(response: unknown): NextFn { - return mock(() => Promise.resolve(response)) as unknown as NextFn; + return vi.fn(() => Promise.resolve(response)) as unknown as NextFn; } /** Create a mock `next` that rejects with the given error. */ function mockNextReject(error: Error): NextFn { - return mock(() => Promise.reject(error)) as unknown as NextFn; + return vi.fn(() => Promise.reject(error)) as unknown as NextFn; } const TEST_WORKSPACE = { diff --git a/apps/server/src/routes/slack/blocks.test.ts b/apps/server/src/routes/slack/blocks.test.ts index b5a7170d1..7b7c1a434 100644 --- a/apps/server/src/routes/slack/blocks.test.ts +++ b/apps/server/src/routes/slack/blocks.test.ts @@ -1,6 +1,5 @@ import { agentTools } from "@openstatus/services/agent-tools"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { buildConfirmationBlocks, diff --git a/apps/server/src/routes/slack/channel-context.test.ts b/apps/server/src/routes/slack/channel-context.test.ts index 8c3fce7e6..608ebc093 100644 --- a/apps/server/src/routes/slack/channel-context.test.ts +++ b/apps/server/src/routes/slack/channel-context.test.ts @@ -1,6 +1,6 @@ -import { beforeEach, describe, expect, test } from "@openstatus/test-utils"; // The double stands in for @slack/web-api via the test import map. import { WebClient } from "@slack/web-api"; +import { beforeEach, describe, expect, test } from "vitest"; import { slackTestState } from "@/libs/test/doubles/slack-test-state"; diff --git a/apps/server/src/routes/slack/commands.test.ts b/apps/server/src/routes/slack/commands.test.ts index 765e171a3..7205fa4c4 100644 --- a/apps/server/src/routes/slack/commands.test.ts +++ b/apps/server/src/routes/slack/commands.test.ts @@ -10,8 +10,8 @@ import { workspace as workspaceTable, } from "@openstatus/db/src/schema"; import { declareIncident } from "@openstatus/services/incident"; -import { beforeEach, describe, expect, test } from "@openstatus/test-utils"; import { Hono } from "hono"; +import { beforeEach, describe, expect, test } from "vitest"; import { slackTestState } from "@/libs/test/doubles/slack-test-state"; import { diff --git a/apps/server/src/routes/slack/config.ts b/apps/server/src/routes/slack/config.ts index 39fa615a3..c149be685 100644 --- a/apps/server/src/routes/slack/config.ts +++ b/apps/server/src/routes/slack/config.ts @@ -5,9 +5,8 @@ import { env } from "@/env"; * built and carried on the request context. * * Handlers read this instead of `env` directly so tests can construct a route - * with explicit config. Mutating `process.env` per-test is not viable: Deno's - * `--parallel` runs test files in workers that share one process environment, - * so concurrent files clobber each other's settings. + * with explicit config. Mutating `process.env` per-test is not viable: every + * test in a file shares it, so one case's settings leak into the next. */ export type SlackConfig = { signingSecret?: string; diff --git a/apps/server/src/routes/slack/confirmation-store.test.ts b/apps/server/src/routes/slack/confirmation-store.test.ts index d8bb07c14..3b2aed787 100644 --- a/apps/server/src/routes/slack/confirmation-store.test.ts +++ b/apps/server/src/routes/slack/confirmation-store.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { beforeEach, describe, test } from "@std/testing/bdd"; +import { beforeEach, describe, expect, test } from "vitest"; import { consume, diff --git a/apps/server/src/routes/slack/handler.test.ts b/apps/server/src/routes/slack/handler.test.ts index 7ec204863..c3c62d6d7 100644 --- a/apps/server/src/routes/slack/handler.test.ts +++ b/apps/server/src/routes/slack/handler.test.ts @@ -15,14 +15,8 @@ import { createTestWorkspace, createUser, } from "@openstatus/db/src/test/factories"; -import { - afterEach, - beforeEach, - describe, - expect, - test, -} from "@openstatus/test-utils"; import { Hono } from "hono"; +import { afterEach, beforeEach, describe, expect, test } from "vitest"; // workspace-resolver / @slack/web-api / agent are swapped for doubles via the // test import map; behavior is driven through this shared mutable state. diff --git a/apps/server/src/routes/slack/home.test.ts b/apps/server/src/routes/slack/home.test.ts index acd3b1117..11d335484 100644 --- a/apps/server/src/routes/slack/home.test.ts +++ b/apps/server/src/routes/slack/home.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { buildHomeBlocks, OPEN_DECLARE_INCIDENT_ACTION } from "./home"; diff --git a/apps/server/src/routes/slack/incident-commands.test.ts b/apps/server/src/routes/slack/incident-commands.test.ts index 30faf8708..4788926dd 100644 --- a/apps/server/src/routes/slack/incident-commands.test.ts +++ b/apps/server/src/routes/slack/incident-commands.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { parseDeclare, parseTarget } from "./incident-commands"; diff --git a/apps/server/src/routes/slack/index.test.ts b/apps/server/src/routes/slack/index.test.ts index 6cb69fe98..ba4fcf257 100644 --- a/apps/server/src/routes/slack/index.test.ts +++ b/apps/server/src/routes/slack/index.test.ts @@ -1,8 +1,7 @@ import crypto from "node:crypto"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; import { Hono } from "hono"; +import { describe, expect, test } from "vitest"; import type { SlackConfig } from "./config"; import { createSlackRoute } from "./index"; diff --git a/apps/server/src/routes/slack/interactions.test.ts b/apps/server/src/routes/slack/interactions.test.ts index b3aaa9545..3b3039b8a 100644 --- a/apps/server/src/routes/slack/interactions.test.ts +++ b/apps/server/src/routes/slack/interactions.test.ts @@ -18,14 +18,8 @@ import { createUser, } from "@openstatus/db/src/test/factories"; import { declareIncident } from "@openstatus/services/incident"; -import { - afterEach, - beforeEach, - describe, - expect, - test, -} from "@openstatus/test-utils"; import { Hono } from "hono"; +import { afterEach, beforeEach, describe, expect, test } from "vitest"; // workspace-resolver / @slack/web-api are swapped for doubles via the test // import map; behavior is driven through this shared mutable state. diff --git a/apps/server/src/routes/slack/mrkdwn.test.ts b/apps/server/src/routes/slack/mrkdwn.test.ts index 3e00a7723..1bf374018 100644 --- a/apps/server/src/routes/slack/mrkdwn.test.ts +++ b/apps/server/src/routes/slack/mrkdwn.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "@openstatus/test-utils"; +import { describe, expect, test } from "vitest"; import { toMrkdwn } from "./mrkdwn"; diff --git a/apps/server/src/routes/slack/oauth.test.ts b/apps/server/src/routes/slack/oauth.test.ts index 8f718f5d8..a7f48229b 100644 --- a/apps/server/src/routes/slack/oauth.test.ts +++ b/apps/server/src/routes/slack/oauth.test.ts @@ -1,9 +1,8 @@ import crypto from "node:crypto"; import { SLACK_BOT_SCOPES } from "@openstatus/services/integration"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; import { Hono } from "hono"; +import { describe, expect, test } from "vitest"; import { TEST_SIGNING_SECRET as SIGNING_SECRET, diff --git a/apps/server/src/routes/slack/presenters/presenters.test.ts b/apps/server/src/routes/slack/presenters/presenters.test.ts index 5598db039..97661bfb4 100644 --- a/apps/server/src/routes/slack/presenters/presenters.test.ts +++ b/apps/server/src/routes/slack/presenters/presenters.test.ts @@ -1,7 +1,7 @@ import type { ServiceContext } from "@openstatus/services"; import { agentTools } from "@openstatus/services/agent-tools"; import type { AnyAgentTool } from "@openstatus/services/agent-tools"; -import { describe, expect, test } from "@openstatus/test-utils"; +import { describe, expect, test } from "vitest"; import { z } from "zod"; // page-urls.ts hits the db; it's swapped for a deterministic double via the diff --git a/apps/server/src/routes/slack/registry-runner.test.ts b/apps/server/src/routes/slack/registry-runner.test.ts index d0854fad9..f345e6cc3 100644 --- a/apps/server/src/routes/slack/registry-runner.test.ts +++ b/apps/server/src/routes/slack/registry-runner.test.ts @@ -1,8 +1,7 @@ import type { ServiceContext } from "@openstatus/services"; import { agentTools } from "@openstatus/services/agent-tools"; import type { AnyAgentTool } from "@openstatus/services/agent-tools"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { z } from "zod"; import { @@ -249,7 +248,7 @@ describe("executeRegistryAction", () => { summarize: () => ({ title: "fake", lines: [] }), }, }); - expect( + await expect( executeRegistryAction({ tool: t, ctx: fakeCtx, @@ -265,7 +264,7 @@ describe("executeRegistryAction", () => { throw new Error("kaboom"); }, }); - expect( + await expect( executeRegistryAction({ tool: t, ctx: fakeCtx, diff --git a/apps/server/src/routes/slack/resolve-slack-user.test.ts b/apps/server/src/routes/slack/resolve-slack-user.test.ts index 08855ee37..3502033b8 100644 --- a/apps/server/src/routes/slack/resolve-slack-user.test.ts +++ b/apps/server/src/routes/slack/resolve-slack-user.test.ts @@ -10,8 +10,7 @@ import { createUser, } from "@openstatus/db/src/test/factories"; import { WebClient } from "@slack/web-api"; -import { expect } from "@std/expect"; -import { beforeAll, beforeEach, describe, test } from "@std/testing/bdd"; +import { beforeAll, beforeEach, describe, expect, test } from "vitest"; // @slack/web-api is the import-mapped double; `users.info` is driven by // slackTestState.usersInfoImpl. diff --git a/apps/server/src/routes/slack/rich-text.test.ts b/apps/server/src/routes/slack/rich-text.test.ts index 6aa5d6128..240a39010 100644 --- a/apps/server/src/routes/slack/rich-text.test.ts +++ b/apps/server/src/routes/slack/rich-text.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "@openstatus/test-utils"; +import { describe, expect, test } from "vitest"; import { collectMentions, diff --git a/apps/server/src/routes/slack/system-prompt.test.ts b/apps/server/src/routes/slack/system-prompt.test.ts index fb3f29d59..b6c6ca961 100644 --- a/apps/server/src/routes/slack/system-prompt.test.ts +++ b/apps/server/src/routes/slack/system-prompt.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { buildSystemPrompt } from "./system-prompt"; diff --git a/apps/server/src/routes/slack/thread-title.test.ts b/apps/server/src/routes/slack/thread-title.test.ts index ea9fac037..af30cd43d 100644 --- a/apps/server/src/routes/slack/thread-title.test.ts +++ b/apps/server/src/routes/slack/thread-title.test.ts @@ -1,5 +1,5 @@ -import { beforeEach, describe, expect, test } from "@openstatus/test-utils"; import type { WebClient } from "@slack/web-api"; +import { beforeEach, describe, expect, test } from "vitest"; import { buildThreadTitle, diff --git a/apps/server/src/routes/slack/verify.test.ts b/apps/server/src/routes/slack/verify.test.ts index 14240ee68..22ea2de7c 100644 --- a/apps/server/src/routes/slack/verify.test.ts +++ b/apps/server/src/routes/slack/verify.test.ts @@ -1,8 +1,7 @@ import crypto from "node:crypto"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; import { Hono } from "hono"; +import { describe, expect, test } from "vitest"; import { TEST_SIGNING_SECRET as SIGNING_SECRET, diff --git a/apps/server/src/routes/v1/check/http/post.test.ts b/apps/server/src/routes/v1/check/http/post.test.ts index f94500dcb..c8f64e0b4 100644 --- a/apps/server/src/routes/v1/check/http/post.test.ts +++ b/apps/server/src/routes/v1/check/http/post.test.ts @@ -1,8 +1,8 @@ -import { afterEach, expect, mock, test } from "@openstatus/test-utils"; +import { afterEach, expect, test, vi } from "vitest"; import { app } from "@/index"; -const mockFetch = mock(); +const mockFetch = vi.fn(); global.fetch = mockFetch as unknown as typeof fetch; diff --git a/apps/server/src/routes/v1/incidents/get.test.ts b/apps/server/src/routes/v1/incidents/get.test.ts index 9de6b293d..2b5b33e4c 100644 --- a/apps/server/src/routes/v1/incidents/get.test.ts +++ b/apps/server/src/routes/v1/incidents/get.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/incidents/get_all.test.ts b/apps/server/src/routes/v1/incidents/get_all.test.ts index c0c5cf082..cf9405cb5 100644 --- a/apps/server/src/routes/v1/incidents/get_all.test.ts +++ b/apps/server/src/routes/v1/incidents/get_all.test.ts @@ -1,7 +1,6 @@ import { db, eq } from "@openstatus/db"; import { monitorIncidentTable, monitor } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/incidents/put.test.ts b/apps/server/src/routes/v1/incidents/put.test.ts index 8bf5f059e..c2aa59f45 100644 --- a/apps/server/src/routes/v1/incidents/put.test.ts +++ b/apps/server/src/routes/v1/incidents/put.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/maintenances/attribution.test.ts b/apps/server/src/routes/v1/maintenances/attribution.test.ts index f7e55962c..9fba86554 100644 --- a/apps/server/src/routes/v1/maintenances/attribution.test.ts +++ b/apps/server/src/routes/v1/maintenances/attribution.test.ts @@ -1,8 +1,7 @@ import { OpenAPIHono } from "@hono/zod-openapi"; import { db, eq } from "@openstatus/db"; import { maintenance } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { lookupWorkspace } from "@/libs/middlewares/auth"; diff --git a/apps/server/src/routes/v1/maintenances/get.test.ts b/apps/server/src/routes/v1/maintenances/get.test.ts index 69a4a2483..db267b89b 100644 --- a/apps/server/src/routes/v1/maintenances/get.test.ts +++ b/apps/server/src/routes/v1/maintenances/get.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/maintenances/get_all.test.ts b/apps/server/src/routes/v1/maintenances/get_all.test.ts index 2eb761920..07d893845 100644 --- a/apps/server/src/routes/v1/maintenances/get_all.test.ts +++ b/apps/server/src/routes/v1/maintenances/get_all.test.ts @@ -5,8 +5,7 @@ import { monitor, pageComponent, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/maintenances/post.test.ts b/apps/server/src/routes/v1/maintenances/post.test.ts index e6312a1d7..97bff6e92 100644 --- a/apps/server/src/routes/v1/maintenances/post.test.ts +++ b/apps/server/src/routes/v1/maintenances/post.test.ts @@ -1,7 +1,6 @@ import { db, eq } from "@openstatus/db"; import { maintenance } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { beforeEach, test } from "@std/testing/bdd"; +import { beforeEach, expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/maintenances/put.test.ts b/apps/server/src/routes/v1/maintenances/put.test.ts index 1400f196a..6ddaff617 100644 --- a/apps/server/src/routes/v1/maintenances/put.test.ts +++ b/apps/server/src/routes/v1/maintenances/put.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; @@ -97,7 +96,7 @@ test("update only the message", async () => { expect(result.data?.message).toBe("Only Message Updated"); }); -test.ignore("update only the dates", async () => { +test.skip("update only the dates", async () => { const from = new Date(); const to = new Date(from.getTime() + 7200000); // 2 hours later @@ -121,27 +120,24 @@ test.ignore("update only the dates", async () => { expect(result.data?.to).toEqual(to); }); -test.ignore( - "update maintenance with `from` date after `to` date should return 400", - async () => { - const to = new Date(); - const from = new Date(to.getTime() + 3600000); // from is 1 hour after to - - const res = await app.request("/v1/maintenance/1", { - method: "PUT", - headers: { - "x-openstatus-key": "1", - "Content-Type": "application/json", - }, - body: JSON.stringify({ - from: from.toISOString(), - to: to.toISOString(), - }), - }); - - expect(res.status).toBe(400); - }, -); +test.skip("update maintenance with `from` date after `to` date should return 400", async () => { + const to = new Date(); + const from = new Date(to.getTime() + 3600000); // from is 1 hour after to + + const res = await app.request("/v1/maintenance/1", { + method: "PUT", + headers: { + "x-openstatus-key": "1", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + from: from.toISOString(), + to: to.toISOString(), + }), + }); + + expect(res.status).toBe(400); +}); test("remove all maintenance monitors", async () => { const res = await app.request("/v1/maintenance/1", { @@ -162,7 +158,7 @@ test("remove all maintenance monitors", async () => { expect(result.data?.monitorIds?.length).toBe(0); }); -test.ignore("empty body should return 400", async () => { +test.skip("empty body should return 400", async () => { const res = await app.request("/v1/maintenance/1", { method: "PUT", headers: { diff --git a/apps/server/src/routes/v1/monitors/delete.test.ts b/apps/server/src/routes/v1/monitors/delete.test.ts index 9e9adec89..3b5d27d6a 100644 --- a/apps/server/src/routes/v1/monitors/delete.test.ts +++ b/apps/server/src/routes/v1/monitors/delete.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/monitors/get.test.ts b/apps/server/src/routes/v1/monitors/get.test.ts index 15209292c..64fe515b0 100644 --- a/apps/server/src/routes/v1/monitors/get.test.ts +++ b/apps/server/src/routes/v1/monitors/get.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/monitors/get_all.test.ts b/apps/server/src/routes/v1/monitors/get_all.test.ts index 97c7dbedc..dbb7482ac 100644 --- a/apps/server/src/routes/v1/monitors/get_all.test.ts +++ b/apps/server/src/routes/v1/monitors/get_all.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/monitors/post.test.ts b/apps/server/src/routes/v1/monitors/post.test.ts index 84ba06e50..711de0df3 100644 --- a/apps/server/src/routes/v1/monitors/post.test.ts +++ b/apps/server/src/routes/v1/monitors/post.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; import { createErrorSchema } from "@/libs/errors"; diff --git a/apps/server/src/routes/v1/monitors/post_dns.test.ts b/apps/server/src/routes/v1/monitors/post_dns.test.ts index 5a5b3d81f..d97fd72d8 100644 --- a/apps/server/src/routes/v1/monitors/post_dns.test.ts +++ b/apps/server/src/routes/v1/monitors/post_dns.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/monitors/post_http.test.ts b/apps/server/src/routes/v1/monitors/post_http.test.ts index 7ac84591a..f96c19f4c 100644 --- a/apps/server/src/routes/v1/monitors/post_http.test.ts +++ b/apps/server/src/routes/v1/monitors/post_http.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/monitors/post_tcp.test.ts b/apps/server/src/routes/v1/monitors/post_tcp.test.ts index 650a79dca..ddd9a4094 100644 --- a/apps/server/src/routes/v1/monitors/post_tcp.test.ts +++ b/apps/server/src/routes/v1/monitors/post_tcp.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/monitors/put.test.ts b/apps/server/src/routes/v1/monitors/put.test.ts index f9714651f..aaeba27a9 100644 --- a/apps/server/src/routes/v1/monitors/put.test.ts +++ b/apps/server/src/routes/v1/monitors/put.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/monitors/put_dns.test.ts b/apps/server/src/routes/v1/monitors/put_dns.test.ts index a55026b81..2b96d65c9 100644 --- a/apps/server/src/routes/v1/monitors/put_dns.test.ts +++ b/apps/server/src/routes/v1/monitors/put_dns.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/monitors/put_http.test.ts b/apps/server/src/routes/v1/monitors/put_http.test.ts index 12dd5ff79..1ae7b46fa 100644 --- a/apps/server/src/routes/v1/monitors/put_http.test.ts +++ b/apps/server/src/routes/v1/monitors/put_http.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/monitors/put_tcp.test.ts b/apps/server/src/routes/v1/monitors/put_tcp.test.ts index db7db81a7..b10ca8e3c 100644 --- a/apps/server/src/routes/v1/monitors/put_tcp.test.ts +++ b/apps/server/src/routes/v1/monitors/put_tcp.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/monitors/results/get.test.ts b/apps/server/src/routes/v1/monitors/results/get.test.ts index 511ec1bff..7bdd94d7e 100644 --- a/apps/server/src/routes/v1/monitors/results/get.test.ts +++ b/apps/server/src/routes/v1/monitors/results/get.test.ts @@ -1,11 +1,10 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; import { ResultRun } from "../schema"; -test.ignore("get monitor result with valid id should return 200", async () => { +test.skip("get monitor result with valid id should return 200", async () => { const res = await app.request("/v1/monitor/1/result/1", { method: "GET", headers: { @@ -61,23 +60,20 @@ test("get monitor result from different workspace should return 404", async () = expect(res.status).toBe(404); }); -test.ignore( - "get monitor result with valid TCP monitor should return 200", - async () => { - const res = await app.request("/v1/monitor/4/result/2", { - method: "GET", - headers: { - "x-openstatus-key": "1", - }, - }); - - expect(res.status).toBe(200); - - const json = await res.json(); - const result = ResultRun.array().safeParse(json); - expect(result.success).toBe(true); - }, -); +test.skip("get monitor result with valid TCP monitor should return 200", async () => { + const res = await app.request("/v1/monitor/4/result/2", { + method: "GET", + headers: { + "x-openstatus-key": "1", + }, + }); + + expect(res.status).toBe(200); + + const json = await res.json(); + const result = ResultRun.array().safeParse(json); + expect(result.success).toBe(true); +}); test("get monitor result with non-matching result id should return 404", async () => { const res = await app.request("/v1/monitor/1/result/999998", { diff --git a/apps/server/src/routes/v1/monitors/run/post.test.ts b/apps/server/src/routes/v1/monitors/run/post.test.ts index b0a1f7565..29e2b846d 100644 --- a/apps/server/src/routes/v1/monitors/run/post.test.ts +++ b/apps/server/src/routes/v1/monitors/run/post.test.ts @@ -1,10 +1,10 @@ -import { afterEach, expect, mock, test } from "@openstatus/test-utils"; +import { afterEach, expect, test, vi } from "vitest"; import { app } from "@/index"; import { TriggerResult } from "../schema"; -const mockFetch = mock(); +const mockFetch = vi.fn(); global.fetch = mockFetch as unknown as typeof fetch; @@ -56,6 +56,8 @@ test("run monitor with valid id should return 200", async () => { }); test("run monitor with no-wait parameter should return empty array", async () => { + // The handler attaches .catch to each checker request without awaiting it. + mockFetch.mockResolvedValue(new Response("{}")); const res = await app.request("/v1/monitor/1/run?no-wait=true", { method: "POST", headers: { @@ -142,47 +144,44 @@ test("run TCP monitor with valid id should return 200", async () => { } }); -test.ignore( - "run monitor with multiple regions should return array of results", - async () => { - mockFetch.mockReturnValue( - Promise.resolve( - new Response( - JSON.stringify({ - jobType: "http", - status: 200, - latency: 100, - region: "ams", - timestamp: 1234567890, - timing: { - dnsStart: 1, - dnsDone: 2, - connectStart: 3, - connectDone: 4, - tlsHandshakeStart: 5, - tlsHandshakeDone: 6, - firstByteStart: 7, - firstByteDone: 8, - transferStart: 9, - transferDone: 10, - }, - }), - { status: 200, headers: { "content-type": "application/json" } }, - ), +test.skip("run monitor with multiple regions should return array of results", async () => { + mockFetch.mockReturnValue( + Promise.resolve( + new Response( + JSON.stringify({ + jobType: "http", + status: 200, + latency: 100, + region: "ams", + timestamp: 1234567890, + timing: { + dnsStart: 1, + dnsDone: 2, + connectStart: 3, + connectDone: 4, + tlsHandshakeStart: 5, + tlsHandshakeDone: 6, + firstByteStart: 7, + firstByteDone: 8, + transferStart: 9, + transferDone: 10, + }, + }), + { status: 200, headers: { "content-type": "application/json" } }, ), - ); - - const res = await app.request("/v1/monitor/5/run", { - method: "POST", - headers: { - "x-openstatus-key": "1", - "content-type": "application/json", - }, - }); - - expect(res.status).toBe(200); - - const json = await res.json(); - expect(Array.isArray(json)).toBe(true); - }, -); + ), + ); + + const res = await app.request("/v1/monitor/5/run", { + method: "POST", + headers: { + "x-openstatus-key": "1", + "content-type": "application/json", + }, + }); + + expect(res.status).toBe(200); + + const json = await res.json(); + expect(Array.isArray(json)).toBe(true); +}); diff --git a/apps/server/src/routes/v1/monitors/summary/get.test.ts b/apps/server/src/routes/v1/monitors/summary/get.test.ts index 300271c17..bc21298fa 100644 --- a/apps/server/src/routes/v1/monitors/summary/get.test.ts +++ b/apps/server/src/routes/v1/monitors/summary/get.test.ts @@ -1,12 +1,11 @@ import { z } from "@hono/zod-openapi"; -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; import { SummarySchema } from "./schema"; -test.ignore("return the summary of the monitor", async () => { +test.skip("return the summary of the monitor", async () => { const res = await app.request("/v1/monitor/1/summary", { headers: { "x-openstatus-key": "1", diff --git a/apps/server/src/routes/v1/monitors/trigger/post.test.ts b/apps/server/src/routes/v1/monitors/trigger/post.test.ts index f0b322c27..190a74ed8 100644 --- a/apps/server/src/routes/v1/monitors/trigger/post.test.ts +++ b/apps/server/src/routes/v1/monitors/trigger/post.test.ts @@ -1,11 +1,11 @@ import { createMonitor } from "@openstatus/db/src/test/factories"; -import { afterEach, expect, mock, test } from "@openstatus/test-utils"; +import { afterEach, expect, test, vi } from "vitest"; import { app } from "@/index"; import { TriggerSchema } from "./schema"; -const mockFetch = mock(); +const mockFetch = vi.fn(); global.fetch = mockFetch as unknown as typeof fetch; diff --git a/apps/server/src/routes/v1/notifications/get.test.ts b/apps/server/src/routes/v1/notifications/get.test.ts index 8759de392..bf9c410e7 100644 --- a/apps/server/src/routes/v1/notifications/get.test.ts +++ b/apps/server/src/routes/v1/notifications/get.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/notifications/get_all.test.ts b/apps/server/src/routes/v1/notifications/get_all.test.ts index 5f2f580eb..624dc46c5 100644 --- a/apps/server/src/routes/v1/notifications/get_all.test.ts +++ b/apps/server/src/routes/v1/notifications/get_all.test.ts @@ -1,7 +1,6 @@ import { db, eq } from "@openstatus/db"; import { notification } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/notifications/post.test.ts b/apps/server/src/routes/v1/notifications/post.test.ts index d65ce7dee..c1c130f53 100644 --- a/apps/server/src/routes/v1/notifications/post.test.ts +++ b/apps/server/src/routes/v1/notifications/post.test.ts @@ -1,7 +1,6 @@ import { db, eq } from "@openstatus/db"; import { notification } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/pageSubscribers/post.test.ts b/apps/server/src/routes/v1/pageSubscribers/post.test.ts index 1aad898fc..5a5973b28 100644 --- a/apps/server/src/routes/v1/pageSubscribers/post.test.ts +++ b/apps/server/src/routes/v1/pageSubscribers/post.test.ts @@ -1,9 +1,7 @@ import { and, db, eq, isNull } from "@openstatus/db"; import { pageSubscriber } from "@openstatus/db/src/schema"; import { delivery, resend } from "@openstatus/emails/src/send"; -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; -import { assertSpyCalls, stub } from "@std/testing/mock"; +import { expect, test, vi } from "vitest"; import { app } from "@/index"; @@ -33,10 +31,12 @@ test("create a page subscription", async () => { }); test("sends the same redesigned verification email as the status page form", async () => { - const enabled = stub(delivery, "enabled", () => true); - const send = stub(resend.emails, "send", () => - Promise.resolve({ data: { id: "email_1" }, error: null } as never), - ); + const enabled = vi.spyOn(delivery, "enabled").mockImplementation(() => true); + const send = vi + .spyOn(resend.emails, "send") + .mockImplementation(() => + Promise.resolve({ data: { id: "email_1" }, error: null } as never), + ); const email = "verify-mail@openstatus.dev"; const cleanup = () => @@ -54,20 +54,23 @@ test("sends the same redesigned verification email as the status page form", asy }); expect(res.status).toBe(200); - assertSpyCalls(send, 1); - const payload = send.calls[0].args[0]; + expect(send).toHaveBeenCalledTimes(1); + const payload = send.mock.calls[0][0]; expect(payload.to).toEqual([email]); expect(payload.from).toBe( "Status Page ", ); expect(payload.subject).toMatch(/^Confirm your subscription to /); // the route calls the template as a function, so this is its - expect(payload.react.props.brand.name).toMatch(/Status$/); - expect(payload.react.props.pill.label).toBe("Confirm"); + const layout = payload.react as unknown as { + props: { brand: { name: string }; pill: { label: string } }; + }; + expect(layout.props.brand.name).toMatch(/Status$/); + expect(layout.props.pill.label).toBe("Confirm"); } finally { await cleanup(); - send.restore(); - enabled.restore(); + send.mockRestore(); + enabled.mockRestore(); } }); diff --git a/apps/server/src/routes/v1/pages/get.test.ts b/apps/server/src/routes/v1/pages/get.test.ts index 4335e569d..d6012e05f 100644 --- a/apps/server/src/routes/v1/pages/get.test.ts +++ b/apps/server/src/routes/v1/pages/get.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/pages/get_all.test.ts b/apps/server/src/routes/v1/pages/get_all.test.ts index 6c9485527..26753c7c5 100644 --- a/apps/server/src/routes/v1/pages/get_all.test.ts +++ b/apps/server/src/routes/v1/pages/get_all.test.ts @@ -1,7 +1,6 @@ import { db, eq } from "@openstatus/db"; import { monitor, page, pageComponent } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/pages/post.test.ts b/apps/server/src/routes/v1/pages/post.test.ts index e57a260c0..4d483327c 100644 --- a/apps/server/src/routes/v1/pages/post.test.ts +++ b/apps/server/src/routes/v1/pages/post.test.ts @@ -1,7 +1,6 @@ import { db, eq } from "@openstatus/db"; import { monitor, page, pageComponent } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/pages/put.test.ts b/apps/server/src/routes/v1/pages/put.test.ts index 0c76046de..c01455c60 100644 --- a/apps/server/src/routes/v1/pages/put.test.ts +++ b/apps/server/src/routes/v1/pages/put.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/statusReportUpdates/get.test.ts b/apps/server/src/routes/v1/statusReportUpdates/get.test.ts index 510c03b86..21a4b2942 100644 --- a/apps/server/src/routes/v1/statusReportUpdates/get.test.ts +++ b/apps/server/src/routes/v1/statusReportUpdates/get.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/statusReportUpdates/post.test.ts b/apps/server/src/routes/v1/statusReportUpdates/post.test.ts index d07c2f315..eedb2217d 100644 --- a/apps/server/src/routes/v1/statusReportUpdates/post.test.ts +++ b/apps/server/src/routes/v1/statusReportUpdates/post.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { beforeEach, test } from "@std/testing/bdd"; +import { beforeEach, expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/statusReports/attribution.test.ts b/apps/server/src/routes/v1/statusReports/attribution.test.ts index 99f97c113..68c4ffdbe 100644 --- a/apps/server/src/routes/v1/statusReports/attribution.test.ts +++ b/apps/server/src/routes/v1/statusReports/attribution.test.ts @@ -1,8 +1,7 @@ import { OpenAPIHono } from "@hono/zod-openapi"; import { db, eq } from "@openstatus/db"; import { statusReport, statusReportUpdate } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { lookupWorkspace } from "@/libs/middlewares/auth"; diff --git a/apps/server/src/routes/v1/statusReports/delete.test.ts b/apps/server/src/routes/v1/statusReports/delete.test.ts index b0760725b..6ad5cb2b3 100644 --- a/apps/server/src/routes/v1/statusReports/delete.test.ts +++ b/apps/server/src/routes/v1/statusReports/delete.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/statusReports/get.test.ts b/apps/server/src/routes/v1/statusReports/get.test.ts index 61980dfde..e7e467dce 100644 --- a/apps/server/src/routes/v1/statusReports/get.test.ts +++ b/apps/server/src/routes/v1/statusReports/get.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/statusReports/get_all.test.ts b/apps/server/src/routes/v1/statusReports/get_all.test.ts index 93e057df5..4fb1414ec 100644 --- a/apps/server/src/routes/v1/statusReports/get_all.test.ts +++ b/apps/server/src/routes/v1/statusReports/get_all.test.ts @@ -6,8 +6,7 @@ import { statusReportUpdate, statusReportsToPageComponents, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/statusReports/post.test.ts b/apps/server/src/routes/v1/statusReports/post.test.ts index 94c8d9058..6310181f9 100644 --- a/apps/server/src/routes/v1/statusReports/post.test.ts +++ b/apps/server/src/routes/v1/statusReports/post.test.ts @@ -4,8 +4,7 @@ import { statusReport, statusReportsToPageComponents, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { beforeEach, test } from "@std/testing/bdd"; +import { beforeEach, expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/statusReports/subscriber-filtering.integration.test.ts b/apps/server/src/routes/v1/statusReports/subscriber-filtering.integration.test.ts index 95d14722d..352787ab3 100644 --- a/apps/server/src/routes/v1/statusReports/subscriber-filtering.integration.test.ts +++ b/apps/server/src/routes/v1/statusReports/subscriber-filtering.integration.test.ts @@ -1,7 +1,6 @@ import { and, db, eq, isNotNull, isNull } from "@openstatus/db"; import { page, pageSubscriber } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; /** * Integration tests for subscriber filtering in status report email queries. diff --git a/apps/server/src/routes/v1/statusReports/update/post.test.ts b/apps/server/src/routes/v1/statusReports/update/post.test.ts index cf13fc176..83604a96f 100644 --- a/apps/server/src/routes/v1/statusReports/update/post.test.ts +++ b/apps/server/src/routes/v1/statusReports/update/post.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/src/routes/v1/whoami/get.test.ts b/apps/server/src/routes/v1/whoami/get.test.ts index a0a7aa1b2..34839bbe4 100644 --- a/apps/server/src/routes/v1/whoami/get.test.ts +++ b/apps/server/src/routes/v1/whoami/get.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { app } from "@/index"; diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json index 004eda196..51e03dfaa 100644 --- a/apps/server/tsconfig.json +++ b/apps/server/tsconfig.json @@ -9,16 +9,12 @@ "jsxImportSource": "react", "allowJs": true, "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], + "@openstatus/analytics-real": ["../../packages/analytics/src/index.ts"], + "@openstatus/subscriptions-real": [ + "../../packages/subscriptions/src/index.ts" + ], + "@openstatus/tinybird-real": ["../../packages/tinybird/src/index.ts"] } - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/apps/server/vitest.config.ts b/apps/server/vitest.config.ts new file mode 100644 index 000000000..8b57687a1 --- /dev/null +++ b/apps/server/vitest.config.ts @@ -0,0 +1,6 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname, { + importMap: "test.importmap.json", + envFile: ".env.test", +}); diff --git a/apps/status-page/package.json b/apps/status-page/package.json index 17eeb0ad8..9ec316bfd 100644 --- a/apps/status-page/package.json +++ b/apps/status-page/package.json @@ -8,7 +8,7 @@ "build": "next build", "start": "next start", "lint": "next lint", - "test": "deno test --parallel -A --no-check --sloppy-imports --import-map=test.importmap.json", + "test": "vitest run", "tsc": "tsc --noEmit" }, "dependencies": { @@ -82,9 +82,8 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@tailwindcss/postcss": "catalog:", "@tailwindcss/typography": "catalog:", "@types/dom-speech-recognition": "catalog:", @@ -94,6 +93,7 @@ "shadcn": "catalog:", "tailwindcss": "catalog:", "tw-animate-css": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/apps/status-page/src/components/button/buttons.test.tsx b/apps/status-page/src/components/button/buttons.test.tsx index ea891d4c2..4a3d0abe3 100644 --- a/apps/status-page/src/components/button/buttons.test.tsx +++ b/apps/status-page/src/components/button/buttons.test.tsx @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { ChartTooltipNumber } from "../chart/chart-tooltip-number"; import { PopoverQuantile } from "../popover/popover-quantile"; diff --git a/apps/status-page/src/components/common/link.test.tsx b/apps/status-page/src/components/common/link.test.tsx index 2ad2294d0..d1975e0d6 100644 --- a/apps/status-page/src/components/common/link.test.tsx +++ b/apps/status-page/src/components/common/link.test.tsx @@ -1,7 +1,6 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { NuqsTestingAdapter } from "nuqs/adapters/testing"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { Link } from "./link"; diff --git a/apps/status-page/src/components/content/process-message.test.tsx b/apps/status-page/src/components/content/process-message.test.tsx index 75db588b1..1bf33fa51 100644 --- a/apps/status-page/src/components/content/process-message.test.tsx +++ b/apps/status-page/src/components/content/process-message.test.tsx @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { ProcessMessage } from "./process-message"; @@ -43,7 +42,7 @@ describe("ProcessMessage", () => { }); // the `ol` override drops its props, so ordered list items vanish - it.ignore("keeps the items of ordered lists", () => { + it.skip("keeps the items of ordered lists", () => { const html = render("1. first\n2. second"); expect(html).toContain("list-decimal"); expect(html).toContain("
  • first
  • "); diff --git a/apps/status-page/src/components/date-picker.test.tsx b/apps/status-page/src/components/date-picker.test.tsx index eb47b7471..7d2a9cee2 100644 --- a/apps/status-page/src/components/date-picker.test.tsx +++ b/apps/status-page/src/components/date-picker.test.tsx @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { DatePicker } from "./date-picker"; diff --git a/apps/status-page/src/components/forms/form-manage-subscription.test.tsx b/apps/status-page/src/components/forms/form-manage-subscription.test.tsx index 63af75e18..48d2e4a7d 100644 --- a/apps/status-page/src/components/forms/form-manage-subscription.test.tsx +++ b/apps/status-page/src/components/forms/form-manage-subscription.test.tsx @@ -1,7 +1,6 @@ import type { RouterOutputs } from "@openstatus/api"; -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { FormManageSubscription } from "./form-manage-subscription"; diff --git a/apps/status-page/src/components/forms/forms.test.tsx b/apps/status-page/src/components/forms/forms.test.tsx index 979a1cd42..66cdb3e45 100644 --- a/apps/status-page/src/components/forms/forms.test.tsx +++ b/apps/status-page/src/components/forms/forms.test.tsx @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { FormCard, diff --git a/apps/status-page/src/components/locale-switcher.test.tsx b/apps/status-page/src/components/locale-switcher.test.tsx index bedb8d2b2..e3d0a5c97 100644 --- a/apps/status-page/src/components/locale-switcher.test.tsx +++ b/apps/status-page/src/components/locale-switcher.test.tsx @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { LocaleSwitcher } from "./locale-switcher"; diff --git a/apps/status-page/src/components/status-page/status-component-group.test.tsx b/apps/status-page/src/components/status-page/status-component-group.test.tsx index 7a8a10bb9..e7f223ff8 100644 --- a/apps/status-page/src/components/status-page/status-component-group.test.tsx +++ b/apps/status-page/src/components/status-page/status-component-group.test.tsx @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { StatusComponentGroup } from "./status-component-group"; diff --git a/apps/status-page/src/components/status-page/status-feed.test.tsx b/apps/status-page/src/components/status-page/status-feed.test.tsx index a154a186f..66f915a8f 100644 --- a/apps/status-page/src/components/status-page/status-feed.test.tsx +++ b/apps/status-page/src/components/status-page/status-feed.test.tsx @@ -1,7 +1,6 @@ -import { expect } from "@std/expect"; -import { afterEach, describe, it } from "@std/testing/bdd"; import { NuqsTestingAdapter } from "nuqs/adapters/testing"; import { renderToStaticMarkup } from "react-dom/server"; +import { afterEach, describe, expect, it } from "vitest"; import { pathnamePrefix } from "@/test/use-pathname-prefix.mock"; diff --git a/apps/status-page/src/components/status-page/utils.test.ts b/apps/status-page/src/components/status-page/utils.test.ts index ea5fa252d..1e776f375 100644 --- a/apps/status-page/src/components/status-page/utils.test.ts +++ b/apps/status-page/src/components/status-page/utils.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { type ChartData, diff --git a/apps/status-page/src/components/themes/theme-builder-provider.test.tsx b/apps/status-page/src/components/themes/theme-builder-provider.test.tsx index ebb5dfd6b..458120330 100644 --- a/apps/status-page/src/components/themes/theme-builder-provider.test.tsx +++ b/apps/status-page/src/components/themes/theme-builder-provider.test.tsx @@ -1,8 +1,7 @@ import { THEMES } from "@openstatus/theme-store"; -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { NuqsTestingAdapter } from "nuqs/adapters/testing"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { ThemePromptButton } from "./theme-agent-actions"; import { diff --git a/apps/status-page/src/components/ui/data-table/data-table-pagination.test.tsx b/apps/status-page/src/components/ui/data-table/data-table-pagination.test.tsx index 3c227689e..427d24dd8 100644 --- a/apps/status-page/src/components/ui/data-table/data-table-pagination.test.tsx +++ b/apps/status-page/src/components/ui/data-table/data-table-pagination.test.tsx @@ -1,5 +1,3 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { type ColumnDef, type SortingState, @@ -9,6 +7,7 @@ import { useReactTable, } from "@tanstack/react-table"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; import { DataTableColumnHeader } from "./data-table-column-header"; import { diff --git a/apps/status-page/src/content/markdown/generators.test.ts b/apps/status-page/src/content/markdown/generators.test.ts index bef405019..e15c546d0 100644 --- a/apps/status-page/src/content/markdown/generators.test.ts +++ b/apps/status-page/src/content/markdown/generators.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { escapeCell, diff --git a/apps/status-page/src/content/markdown/helpers.test.ts b/apps/status-page/src/content/markdown/helpers.test.ts index 9b5495769..b27be5785 100644 --- a/apps/status-page/src/content/markdown/helpers.test.ts +++ b/apps/status-page/src/content/markdown/helpers.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { canonicalUrl, diff --git a/apps/status-page/src/content/markdown/match-route.test.ts b/apps/status-page/src/content/markdown/match-route.test.ts index 4a4175fd9..06bf19942 100644 --- a/apps/status-page/src/content/markdown/match-route.test.ts +++ b/apps/status-page/src/content/markdown/match-route.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { matchMarkdownRoute, parseMarkdownPath } from "./match-route"; diff --git a/apps/status-page/src/content/status-json.test.ts b/apps/status-page/src/content/status-json.test.ts index 37931027d..6a49c74d2 100644 --- a/apps/status-page/src/content/status-json.test.ts +++ b/apps/status-page/src/content/status-json.test.ts @@ -1,6 +1,5 @@ import type { RouterOutputs } from "@openstatus/api"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { matchEndpoint, diff --git a/apps/status-page/src/content/status-vocab.test.ts b/apps/status-page/src/content/status-vocab.test.ts index da2f5bb64..9cea57f4e 100644 --- a/apps/status-page/src/content/status-vocab.test.ts +++ b/apps/status-page/src/content/status-vocab.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { componentStatus, diff --git a/apps/status-page/src/data/actions.client.test.ts b/apps/status-page/src/data/actions.client.test.ts index 5a04c56a8..34e3d23c4 100644 --- a/apps/status-page/src/data/actions.client.test.ts +++ b/apps/status-page/src/data/actions.client.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import * as incidents from "./incidents.client"; import * as maintenances from "./maintenances.client"; diff --git a/apps/status-page/src/data/metrics.client.test.ts b/apps/status-page/src/data/metrics.client.test.ts index b94027964..03d5aafa1 100644 --- a/apps/status-page/src/data/metrics.client.test.ts +++ b/apps/status-page/src/data/metrics.client.test.ts @@ -1,6 +1,5 @@ import type { RouterOutputs } from "@openstatus/api"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { getMonitorListMetrics, diff --git a/apps/status-page/src/i18n/messages.test.ts b/apps/status-page/src/i18n/messages.test.ts index eea5fb455..825f6748b 100644 --- a/apps/status-page/src/i18n/messages.test.ts +++ b/apps/status-page/src/i18n/messages.test.ts @@ -2,8 +2,7 @@ import { Buffer } from "node:buffer"; import { readFile } from "node:fs/promises"; import { defaultLocale, locales } from "@openstatus/locales"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; // next-intl's extractor derives ids from the source text; catalogs drift // silently when that format changes (4.14 moved base64 -> base64url). diff --git a/apps/status-page/src/lib/alternates.test.ts b/apps/status-page/src/lib/alternates.test.ts index 320be3b7d..53091e3bd 100644 --- a/apps/status-page/src/lib/alternates.test.ts +++ b/apps/status-page/src/lib/alternates.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { statusPageAlternates } from "./alternates"; diff --git a/apps/status-page/src/lib/base-url.test.ts b/apps/status-page/src/lib/base-url.test.ts index 157d28dd6..8ca787862 100644 --- a/apps/status-page/src/lib/base-url.test.ts +++ b/apps/status-page/src/lib/base-url.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, it } from "@std/testing/bdd"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { getBaseUrl } from "./base-url"; diff --git a/apps/status-page/src/lib/chart.test.ts b/apps/status-page/src/lib/chart.test.ts index 28ec02780..1d7366979 100644 --- a/apps/status-page/src/lib/chart.test.ts +++ b/apps/status-page/src/lib/chart.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { getPayloadConfigFromPayload } from "./chart"; diff --git a/apps/status-page/src/lib/composition.test.ts b/apps/status-page/src/lib/composition.test.ts index a65777454..f4bfea304 100644 --- a/apps/status-page/src/lib/composition.test.ts +++ b/apps/status-page/src/lib/composition.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { composeEventHandlers, composeRefs } from "./composition"; diff --git a/apps/status-page/src/lib/domain.test.ts b/apps/status-page/src/lib/domain.test.ts index 7483aca24..15f66518d 100644 --- a/apps/status-page/src/lib/domain.test.ts +++ b/apps/status-page/src/lib/domain.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { getValidSubdomain, stripHostPort } from "./domain"; diff --git a/apps/status-page/src/lib/embed-params.test.ts b/apps/status-page/src/lib/embed-params.test.ts index 560a9ee18..ab109476f 100644 --- a/apps/status-page/src/lib/embed-params.test.ts +++ b/apps/status-page/src/lib/embed-params.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { ALL_EMBED_SECTIONS, diff --git a/apps/status-page/src/lib/formatter.test.ts b/apps/status-page/src/lib/formatter.test.ts index 1e832ebc6..9ca20f914 100644 --- a/apps/status-page/src/lib/formatter.test.ts +++ b/apps/status-page/src/lib/formatter.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { formatDate, diff --git a/apps/status-page/src/lib/http/etag.test.ts b/apps/status-page/src/lib/http/etag.test.ts index 66d584e39..78fad545b 100644 --- a/apps/status-page/src/lib/http/etag.test.ts +++ b/apps/status-page/src/lib/http/etag.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { computeETag, isNotModified } from "./etag"; diff --git a/apps/status-page/src/lib/http/markdown-response.test.ts b/apps/status-page/src/lib/http/markdown-response.test.ts index 00976f941..3749c6b5a 100644 --- a/apps/status-page/src/lib/http/markdown-response.test.ts +++ b/apps/status-page/src/lib/http/markdown-response.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { withPoweredBy } from "../../content/markdown"; import { computeETag } from "./etag"; diff --git a/apps/status-page/src/lib/proxy/apply-page-locale-override.test.ts b/apps/status-page/src/lib/proxy/apply-page-locale-override.test.ts index c04f0fe62..bc346bd27 100644 --- a/apps/status-page/src/lib/proxy/apply-page-locale-override.test.ts +++ b/apps/status-page/src/lib/proxy/apply-page-locale-override.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import type { ResolvedRoute } from "../resolve-route"; import { applyPageLocaleOverride } from "./apply-page-locale-override"; diff --git a/apps/status-page/src/lib/proxy/apply-page-slug-prefix.test.ts b/apps/status-page/src/lib/proxy/apply-page-slug-prefix.test.ts index f4083a0de..e8dd10220 100644 --- a/apps/status-page/src/lib/proxy/apply-page-slug-prefix.test.ts +++ b/apps/status-page/src/lib/proxy/apply-page-slug-prefix.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import type { ResolvedRoute } from "../resolve-route"; import { applyPageLocaleOverride } from "./apply-page-locale-override"; diff --git a/apps/status-page/src/lib/proxy/build-external-path.test.ts b/apps/status-page/src/lib/proxy/build-external-path.test.ts index df5444bb4..39ff5e891 100644 --- a/apps/status-page/src/lib/proxy/build-external-path.test.ts +++ b/apps/status-page/src/lib/proxy/build-external-path.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { buildExternalPath } from "./build-external-path"; diff --git a/apps/status-page/src/lib/proxy/compose-page-action.test.ts b/apps/status-page/src/lib/proxy/compose-page-action.test.ts index 8b6d4e678..6a90a6572 100644 --- a/apps/status-page/src/lib/proxy/compose-page-action.test.ts +++ b/apps/status-page/src/lib/proxy/compose-page-action.test.ts @@ -1,6 +1,5 @@ import type { Page } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import type { ResolvedRoute } from "../resolve-route"; import { type ComposeInput, composePageAction } from "./compose-page-action"; diff --git a/apps/status-page/src/lib/proxy/detect-markdown.test.ts b/apps/status-page/src/lib/proxy/detect-markdown.test.ts index 75d0c6135..8b4d134a8 100644 --- a/apps/status-page/src/lib/proxy/detect-markdown.test.ts +++ b/apps/status-page/src/lib/proxy/detect-markdown.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { detectMarkdown } from "./detect-markdown"; diff --git a/apps/status-page/src/lib/proxy/markdown-cache-control.test.ts b/apps/status-page/src/lib/proxy/markdown-cache-control.test.ts index 0f8cd54f2..321bdc7ee 100644 --- a/apps/status-page/src/lib/proxy/markdown-cache-control.test.ts +++ b/apps/status-page/src/lib/proxy/markdown-cache-control.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { markdownCacheControl } from "./markdown-cache-control"; diff --git a/apps/status-page/src/lib/proxy/proxy-chain.test.ts b/apps/status-page/src/lib/proxy/proxy-chain.test.ts index 8090223c1..c1e826cca 100644 --- a/apps/status-page/src/lib/proxy/proxy-chain.test.ts +++ b/apps/status-page/src/lib/proxy/proxy-chain.test.ts @@ -1,6 +1,5 @@ import type { Page } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { resolveRoute } from "../resolve-route"; import { applyPageLocaleOverride } from "./apply-page-locale-override"; diff --git a/apps/status-page/src/lib/proxy/resolve-default-rewrite.test.ts b/apps/status-page/src/lib/proxy/resolve-default-rewrite.test.ts index 5aff1bca9..d234e1e55 100644 --- a/apps/status-page/src/lib/proxy/resolve-default-rewrite.test.ts +++ b/apps/status-page/src/lib/proxy/resolve-default-rewrite.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import type { ResolvedRoute } from "../resolve-route"; import { resolveDefaultRewrite } from "./resolve-default-rewrite"; diff --git a/apps/status-page/src/lib/proxy/resolve-email-domain-action.test.ts b/apps/status-page/src/lib/proxy/resolve-email-domain-action.test.ts index 0a47a7efe..7f068b5d5 100644 --- a/apps/status-page/src/lib/proxy/resolve-email-domain-action.test.ts +++ b/apps/status-page/src/lib/proxy/resolve-email-domain-action.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import type { ResolvedRoute } from "../resolve-route"; import { resolveEmailDomainAction } from "./resolve-email-domain-action"; diff --git a/apps/status-page/src/lib/proxy/resolve-ip-restriction-action.test.ts b/apps/status-page/src/lib/proxy/resolve-ip-restriction-action.test.ts index 58987fb9f..d22660c42 100644 --- a/apps/status-page/src/lib/proxy/resolve-ip-restriction-action.test.ts +++ b/apps/status-page/src/lib/proxy/resolve-ip-restriction-action.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import type { ResolvedRoute } from "../resolve-route"; import { resolveIpRestrictionAction } from "./resolve-ip-restriction-action"; diff --git a/apps/status-page/src/lib/proxy/resolve-locale-action.test.ts b/apps/status-page/src/lib/proxy/resolve-locale-action.test.ts index ef78a82cc..4b98d550e 100644 --- a/apps/status-page/src/lib/proxy/resolve-locale-action.test.ts +++ b/apps/status-page/src/lib/proxy/resolve-locale-action.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import type { ResolvedRoute } from "../resolve-route"; import { resolveLocaleAction } from "./resolve-locale-action"; diff --git a/apps/status-page/src/lib/proxy/resolve-password-action.test.ts b/apps/status-page/src/lib/proxy/resolve-password-action.test.ts index 382f8137b..44cc0d770 100644 --- a/apps/status-page/src/lib/proxy/resolve-password-action.test.ts +++ b/apps/status-page/src/lib/proxy/resolve-password-action.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import type { ResolvedRoute } from "../resolve-route"; import { resolvePasswordAction } from "./resolve-password-action"; diff --git a/apps/status-page/src/lib/proxy/resolve-unresolved-host-action.test.ts b/apps/status-page/src/lib/proxy/resolve-unresolved-host-action.test.ts index 67d7a8b9e..4d267e54d 100644 --- a/apps/status-page/src/lib/proxy/resolve-unresolved-host-action.test.ts +++ b/apps/status-page/src/lib/proxy/resolve-unresolved-host-action.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { NOT_FOUND_PATH, diff --git a/apps/status-page/src/lib/proxy/sanitize-redirect-param.test.ts b/apps/status-page/src/lib/proxy/sanitize-redirect-param.test.ts index 5a3c69897..1c9842fee 100644 --- a/apps/status-page/src/lib/proxy/sanitize-redirect-param.test.ts +++ b/apps/status-page/src/lib/proxy/sanitize-redirect-param.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { sanitizeRedirectParam } from "./sanitize-redirect-param"; diff --git a/apps/status-page/src/lib/proxy/strip-prefix-for-external.test.ts b/apps/status-page/src/lib/proxy/strip-prefix-for-external.test.ts index 041ad0a4c..5ac5e12ca 100644 --- a/apps/status-page/src/lib/proxy/strip-prefix-for-external.test.ts +++ b/apps/status-page/src/lib/proxy/strip-prefix-for-external.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { stripPrefixForExternal } from "./strip-prefix-for-external"; diff --git a/apps/status-page/src/lib/report-impacts.test.ts b/apps/status-page/src/lib/report-impacts.test.ts index 12d736683..12886533f 100644 --- a/apps/status-page/src/lib/report-impacts.test.ts +++ b/apps/status-page/src/lib/report-impacts.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { updatesWithImpactChanges } from "./report-impacts"; diff --git a/apps/status-page/src/lib/resolve-pathname-prefix.test.ts b/apps/status-page/src/lib/resolve-pathname-prefix.test.ts index e76cf185f..4dba85fc3 100644 --- a/apps/status-page/src/lib/resolve-pathname-prefix.test.ts +++ b/apps/status-page/src/lib/resolve-pathname-prefix.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { resolvePathnamePrefix } from "./resolve-pathname-prefix"; diff --git a/apps/status-page/src/lib/resolve-route.test.ts b/apps/status-page/src/lib/resolve-route.test.ts index 78dea75e8..de92f9e2b 100644 --- a/apps/status-page/src/lib/resolve-route.test.ts +++ b/apps/status-page/src/lib/resolve-route.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { resolveRoute } from "./resolve-route"; diff --git a/apps/status-page/src/lib/status-blocks-labels.test.ts b/apps/status-page/src/lib/status-blocks-labels.test.ts index f6878d61d..67a53d76d 100644 --- a/apps/status-page/src/lib/status-blocks-labels.test.ts +++ b/apps/status-page/src/lib/status-blocks-labels.test.ts @@ -1,6 +1,5 @@ import { defaultStatusBlocksLabels as labels } from "@openstatus/ui/components/blocks/status.utils"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; // Status-page timestamps render in UTC, so the user-facing labels carry a // trailing "(UTC)" to tell viewers which zone they are reading (see #2302). diff --git a/apps/status-page/src/lib/theme-draft.test.ts b/apps/status-page/src/lib/theme-draft.test.ts index f9e37ab1e..36d4eb450 100644 --- a/apps/status-page/src/lib/theme-draft.test.ts +++ b/apps/status-page/src/lib/theme-draft.test.ts @@ -1,6 +1,5 @@ import { THEMES } from "@openstatus/theme-store"; -import { expect } from "@std/expect"; -import { afterEach, describe, it } from "@std/testing/bdd"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { THEME_DRAFT_STORAGE_KEY, @@ -9,7 +8,24 @@ import { saveThemeDraft, } from "./theme-draft"; -afterEach(() => sessionStorage.clear()); +// Node only exposes Web Storage by default from v25; a Map-backed stand-in +// keeps the suite independent of the runtime version. +function memoryStorage(): Storage { + const items = new Map(); + return { + get length() { + return items.size; + }, + clear: () => items.clear(), + getItem: (key) => items.get(key) ?? null, + key: (index) => [...items.keys()][index] ?? null, + removeItem: (key) => void items.delete(key), + setItem: (key, value) => void items.set(key, String(value)), + }; +} + +beforeEach(() => vi.stubGlobal("sessionStorage", memoryStorage())); +afterEach(() => vi.unstubAllGlobals()); const theme = Object.values(THEMES)[0]; diff --git a/apps/status-page/src/lib/theme-explorer-host.test.ts b/apps/status-page/src/lib/theme-explorer-host.test.ts index 02d6f5cff..d83335a24 100644 --- a/apps/status-page/src/lib/theme-explorer-host.test.ts +++ b/apps/status-page/src/lib/theme-explorer-host.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { isCanonicalThemeExplorerHost, diff --git a/apps/status-page/src/lib/theme-prompt.test.ts b/apps/status-page/src/lib/theme-prompt.test.ts index 014a891a9..8b198c11c 100644 --- a/apps/status-page/src/lib/theme-prompt.test.ts +++ b/apps/status-page/src/lib/theme-prompt.test.ts @@ -1,6 +1,5 @@ import { THEME_VAR_NAMES, THEMES } from "@openstatus/theme-store"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { generateThemePrompt, diff --git a/apps/status-page/src/test/next-link.mock.tsx b/apps/status-page/src/test/next-link.mock.tsx index 03dcc1836..95afbc9b2 100644 --- a/apps/status-page/src/test/next-link.mock.tsx +++ b/apps/status-page/src/test/next-link.mock.tsx @@ -1,5 +1,5 @@ -// Test double for next/link, swapped in via --import-map: Deno cannot resolve -// Next's extensionless CJS entrypoints. Renders a plain anchor. +// Test double for next/link, swapped in via test.importmap.json. Renders a +// plain anchor. import type * as React from "react"; type LinkProps = Omit, "href"> & { diff --git a/apps/status-page/vitest.config.ts b/apps/status-page/vitest.config.ts new file mode 100644 index 000000000..ed88cbc5c --- /dev/null +++ b/apps/status-page/vitest.config.ts @@ -0,0 +1,5 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname, { + importMap: "test.importmap.json", +}); diff --git a/apps/web/package.json b/apps/web/package.json index 06c713201..8154683de 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -9,7 +9,7 @@ "build": "pnpm build:registry && next build", "start": "next start", "lint": "next lint", - "test": "NODE_ENV=test deno test --parallel -A --no-check --sloppy-imports", + "test": "NODE_ENV=test vitest run", "tsc": "tsc --noEmit" }, "dependencies": { @@ -96,9 +96,8 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@tailwindcss/postcss": "catalog:", "@types/node": "catalog:", "@types/react": "catalog:", @@ -109,6 +108,7 @@ "rehype-slug": "catalog:", "tailwindcss": "catalog:", "typescript": "catalog:", - "unified": "catalog:" + "unified": "catalog:", + "vitest": "catalog:" } } diff --git a/apps/web/src/app/(ai)/mcp/route.test.ts b/apps/web/src/app/(ai)/mcp/route.test.ts index 38f323524..910b740fa 100644 --- a/apps/web/src/app/(ai)/mcp/route.test.ts +++ b/apps/web/src/app/(ai)/mcp/route.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { GET, MCP_TRANSPORT_URL, POST } from "./route"; diff --git a/apps/web/src/app/(landing)/play/checker/parse.test.ts b/apps/web/src/app/(landing)/play/checker/parse.test.ts index 3cd1edc04..2de000329 100644 --- a/apps/web/src/app/(landing)/play/checker/parse.test.ts +++ b/apps/web/src/app/(landing)/play/checker/parse.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { parseCheckerLine, splitStreamLines } from "./parse"; diff --git a/apps/web/src/app/(landing)/play/mcp-health/utils.test.ts b/apps/web/src/app/(landing)/play/mcp-health/utils.test.ts index ba080c634..8745b734c 100644 --- a/apps/web/src/app/(landing)/play/mcp-health/utils.test.ts +++ b/apps/web/src/app/(landing)/play/mcp-health/utils.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { formatResponseBody, diff --git a/apps/web/src/app/(landing)/status/incident-events.test.ts b/apps/web/src/app/(landing)/status/incident-events.test.ts index b2fcfc7e7..2dd91a1d8 100644 --- a/apps/web/src/app/(landing)/status/incident-events.test.ts +++ b/apps/web/src/app/(landing)/status/incident-events.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { type OverlayIncident, diff --git a/apps/web/src/app/(landing)/status/utils.test.ts b/apps/web/src/app/(landing)/status/utils.test.ts index 7339b146e..140810686 100644 --- a/apps/web/src/app/(landing)/status/utils.test.ts +++ b/apps/web/src/app/(landing)/status/utils.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { getComponentFullName } from "./utils"; diff --git a/apps/web/src/content/agent-recovery.test.ts b/apps/web/src/content/agent-recovery.test.ts index 603132800..8e7051b75 100644 --- a/apps/web/src/content/agent-recovery.test.ts +++ b/apps/web/src/content/agent-recovery.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { AGENT_RECOVERY_LINKS, notFoundMarkdown } from "./agent-recovery"; diff --git a/apps/web/src/content/convert.test.ts b/apps/web/src/content/convert.test.ts index 741e52a3d..20970f357 100644 --- a/apps/web/src/content/convert.test.ts +++ b/apps/web/src/content/convert.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { convertMdxToMarkdown } from "./convert"; import { getHomePage, getProductPages } from "./utils"; diff --git a/apps/web/src/content/mdx-components/content-lint.test.ts b/apps/web/src/content/mdx-components/content-lint.test.ts index 3be402978..3dcf15b1f 100644 --- a/apps/web/src/content/mdx-components/content-lint.test.ts +++ b/apps/web/src/content/mdx-components/content-lint.test.ts @@ -2,8 +2,7 @@ import { readFileSync, readdirSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; // Pages built from the section pattern (heading, text cell, `Demo` cell). Blog // posts and docs are prose and stay out of scope. diff --git a/apps/web/src/content/mdx-components/details.test.ts b/apps/web/src/content/mdx-components/details.test.ts index 6db076937..3b6156981 100644 --- a/apps/web/src/content/mdx-components/details.test.ts +++ b/apps/web/src/content/mdx-components/details.test.ts @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, test } from "vitest"; import { Details } from "./details"; diff --git a/apps/web/src/content/utils/search-match.test.ts b/apps/web/src/content/utils/search-match.test.ts index 1642ae881..31f2d35cc 100644 --- a/apps/web/src/content/utils/search-match.test.ts +++ b/apps/web/src/content/utils/search-match.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { WEIGHTS, diff --git a/apps/web/src/lib/cdn-checker/probe.test.ts b/apps/web/src/lib/cdn-checker/probe.test.ts index 1871d007e..69bb95e2c 100644 --- a/apps/web/src/lib/cdn-checker/probe.test.ts +++ b/apps/web/src/lib/cdn-checker/probe.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { afterEach, describe, test } from "@std/testing/bdd"; +import { afterEach, describe, expect, test } from "vitest"; import type { RegionCheckerResponse } from "@/lib/checker/utils"; diff --git a/apps/web/src/lib/cdn-checker/schema.test.ts b/apps/web/src/lib/cdn-checker/schema.test.ts index ab8d72b7e..c7f55ba31 100644 --- a/apps/web/src/lib/cdn-checker/schema.test.ts +++ b/apps/web/src/lib/cdn-checker/schema.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { cdnRegionResponseSchema, cdnSummarySchema } from "./schema"; diff --git a/apps/web/src/lib/cdn-checker/summary.test.ts b/apps/web/src/lib/cdn-checker/summary.test.ts index 51401c76f..532ab53c1 100644 --- a/apps/web/src/lib/cdn-checker/summary.test.ts +++ b/apps/web/src/lib/cdn-checker/summary.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import type { CdnRegionResponse, CdnRegionResult } from "./schema"; import { computeCdnSummary } from "./summary"; diff --git a/apps/web/src/lib/checker/utils.test.ts b/apps/web/src/lib/checker/utils.test.ts index 8cc03eb01..420b76030 100644 --- a/apps/web/src/lib/checker/utils.test.ts +++ b/apps/web/src/lib/checker/utils.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { type Timing, diff --git a/apps/web/src/lib/mcp/health-check.test.ts b/apps/web/src/lib/mcp/health-check.test.ts index 760265b64..108507bae 100644 --- a/apps/web/src/lib/mcp/health-check.test.ts +++ b/apps/web/src/lib/mcp/health-check.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { afterEach, describe, test } from "@std/testing/bdd"; +import { afterEach, describe, expect, test } from "vitest"; import { normalizeUrlForStorage, diff --git a/apps/web/src/lib/metadata/shared-metadata.test.ts b/apps/web/src/lib/metadata/shared-metadata.test.ts index 2c24a4dc9..1408ca3fc 100644 --- a/apps/web/src/lib/metadata/shared-metadata.test.ts +++ b/apps/web/src/lib/metadata/shared-metadata.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import type { MDXData } from "../../content/utils"; import { diff --git a/apps/web/src/lib/metadata/structured-data.test.ts b/apps/web/src/lib/metadata/structured-data.test.ts index 012a82d43..286fd7136 100644 --- a/apps/web/src/lib/metadata/structured-data.test.ts +++ b/apps/web/src/lib/metadata/structured-data.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { getJsonLDOrganization } from "./structured-data"; diff --git a/apps/web/src/lib/monitor/utils.test.ts b/apps/web/src/lib/monitor/utils.test.ts index 63f24b517..d8b3aa37c 100644 --- a/apps/web/src/lib/monitor/utils.test.ts +++ b/apps/web/src/lib/monitor/utils.test.ts @@ -1,6 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; -import { FakeTime } from "@std/testing/time"; +import { describe, expect, test, vi } from "vitest"; import { getDateByPeriod, @@ -31,11 +29,11 @@ describe("getDateByPeriod", () => { const now = new Date(2024, 5, 15, 13, 27, 45, 123); function at(fn: () => T) { - const time = new FakeTime(now); + vi.useFakeTimers({ now }); try { return fn(); } finally { - time.restore(); + vi.useRealTimers(); } } diff --git a/apps/web/src/lib/ratelimit-config.test.ts b/apps/web/src/lib/ratelimit-config.test.ts index d5ce55db4..d5fd3d13b 100644 --- a/apps/web/src/lib/ratelimit-config.test.ts +++ b/apps/web/src/lib/ratelimit-config.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { PLAY_RATE_LIMIT_TIERS, diff --git a/apps/web/src/lib/utils.test.ts b/apps/web/src/lib/utils.test.ts index afc5c5ba6..495a0a8f3 100644 --- a/apps/web/src/lib/utils.test.ts +++ b/apps/web/src/lib/utils.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { cn, @@ -97,7 +96,7 @@ describe("manipulateDate", () => { // midnight is detected by the timestamp ending in "00000", which also // matches any time on a 100-second boundary, e.g. 10:00:00 - test.ignore("keeps a whole-hour end date as is", () => { + test.skip("keeps a whole-hour end date as is", () => { const to = new Date(2024, 0, 2, 10); expect(manipulateDate({ from: undefined, to }).toDate).toBe(to.getTime()); }); @@ -138,7 +137,7 @@ describe("hashIP", () => { ); // the `?? ""` fallback feeds a zero-length key to importKey, which throws - test.ignore( + test.skip( "hashes without a configured secret", withSalt(undefined, async () => { const prev = process.env.CRON_SECRET; diff --git a/apps/web/vitest.config.ts b/apps/web/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/apps/web/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/apps/workflows/AGENTS.md b/apps/workflows/AGENTS.md index 78c0c52d0..c16356b10 100644 --- a/apps/workflows/AGENTS.md +++ b/apps/workflows/AGENTS.md @@ -10,8 +10,8 @@ Constraints that follow from the build: `tsdown.config.ts` (native addon, CJS `__dirname` reads) and ship as a side-car `node_modules` in the image. A new dependency with either trait joins that list and the side-car in `dofigen.yml`. -- Tests run `deno test --parallel`, so test files share one process - environment. Never drive a branch by assigning to `process.env` mid-test — - the assignment leaks into whatever else is running. Resolve config once at - module scope and pass it in, so "credential missing" is a value a test hands - you rather than a global it mutates. +- Tests run on Vitest: each test file gets its own process, but the tests in a + file share `process.env`. Never drive a branch by assigning to `process.env` + mid-test — the assignment leaks into every later test in the file. Resolve + config once at module scope and pass it in, so "credential missing" is a + value a test hands you rather than a global it mutates. diff --git a/apps/workflows/package.json b/apps/workflows/package.json index ca2361e4e..9a30d74c6 100644 --- a/apps/workflows/package.json +++ b/apps/workflows/package.json @@ -4,7 +4,7 @@ "check": "tsc --noEmit", "dev": "NODE_ENV=development node --import @oxc-node/core/register --watch src/serve.ts", "start": "NODE_ENV=production node dist/serve.mjs", - "test": "NODE_ENV=test RESEND_API_KEY=test-key CRON_SECRET=test-secret deno test --parallel -A --no-check --sloppy-imports", + "test": "NODE_ENV=test RESEND_API_KEY=test-key CRON_SECRET=test-secret vitest run", "build": "tsdown" }, "dependencies": { @@ -59,6 +59,7 @@ "@openstatus/tsconfig": "workspace:*", "@oxc-node/core": "catalog:", "tsdown": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/apps/workflows/src/checker/alerting.test.ts b/apps/workflows/src/checker/alerting.test.ts index d07bb5e55..c6a735f9f 100644 --- a/apps/workflows/src/checker/alerting.test.ts +++ b/apps/workflows/src/checker/alerting.test.ts @@ -13,32 +13,32 @@ import { import { afterAll, afterEach, - assertSpyCalls, beforeAll, beforeEach, describe, expect, - type Stub, - stub, test, -} from "@openstatus/test-utils"; + vi, + type MockInstance, +} from "vitest"; -import { checkerAudit } from "../utils/audit-log"; +import { stubPublishAuditLog } from "../test/stub-audit-log"; import { triggerNotifications } from "./alerting"; import { providerToFunction } from "./utils"; -// Deno has no module mocking, so we stub the methods on the real singletons -// that alerting.ts reads at call time. Stubbing per-test resolves them to no-op +// Stub the methods on the real singletons that alerting.ts reads at call time. Stubbing per-test resolves them to no-op // to avoid real HTTP / Tinybird calls and gives a fresh call count each test. // biome-ignore lint/suspicious/noExplicitAny: heterogeneous provider stubs -type AnyStub = Stub; +type AnyStub = MockInstance; let stubs: AnyStub[] = []; const stubSend = ( provider: "email" | "slack" | "discord", verb: "sendAlert" | "sendRecovery" | "sendDegraded", ): AnyStub => { - const s = stub(providerToFunction[provider], verb, () => Promise.resolve()); + const s = vi + .spyOn(providerToFunction[provider], verb) + .mockImplementation(() => Promise.resolve()); stubs.push(s); return s; }; @@ -64,9 +64,7 @@ let noNotifMonitorId: number; beforeEach(() => { stubs = []; // biome-ignore lint/suspicious/noExplicitAny: avoid Tinybird call - stubs.push( - stub(checkerAudit, "publishAuditLog", () => Promise.resolve()) as AnyStub, - ); + stubs.push(stubPublishAuditLog()); mockEmailSendAlert = stubSend("email", "sendAlert"); mockEmailSendRecovery = stubSend("email", "sendRecovery"); mockEmailSendDegraded = stubSend("email", "sendDegraded"); @@ -79,7 +77,7 @@ beforeEach(() => { }); afterEach(() => { - for (const s of stubs) s.restore(); + for (const s of stubs) s.mockRestore(); stubs = []; }); @@ -113,7 +111,7 @@ await describe("triggerNotifications", async () => { latency: 1500, }); - assertSpyCalls(mockEmailSendAlert, 1); + expect(mockEmailSendAlert).toHaveBeenCalledTimes(1); expect(result).toHaveLength(1); expect(result[0]).toEqual({ notificationId: emailNotificationId, @@ -132,7 +130,7 @@ await describe("triggerNotifications", async () => { regions: ["ams"], }); - assertSpyCalls(mockEmailSendRecovery, 1); + expect(mockEmailSendRecovery).toHaveBeenCalledTimes(1); expect(result).toHaveLength(1); expect(result[0]).toEqual({ notificationId: emailNotificationId, @@ -152,7 +150,7 @@ await describe("triggerNotifications", async () => { regions: ["ams"], }); - assertSpyCalls(mockEmailSendDegraded, 1); + expect(mockEmailSendDegraded).toHaveBeenCalledTimes(1); expect(result).toHaveLength(1); expect(result[0]).toEqual({ notificationId: emailNotificationId, @@ -171,7 +169,7 @@ await describe("triggerNotifications", async () => { cronTimestamp, }); - assertSpyCalls(mockEmailSendAlert, 0); + expect(mockEmailSendAlert).toHaveBeenCalledTimes(0); expect(result).toHaveLength(0); }); @@ -186,7 +184,7 @@ await describe("triggerNotifications", async () => { }); expect(first).toHaveLength(1); - assertSpyCalls(mockEmailSendAlert, 1); + expect(mockEmailSendAlert).toHaveBeenCalledTimes(1); // Same cronTimestamp should be skipped due to unique constraint const second = await triggerNotifications({ @@ -197,7 +195,7 @@ await describe("triggerNotifications", async () => { }); // still only the first call — the duplicate was skipped - assertSpyCalls(mockEmailSendAlert, 1); + expect(mockEmailSendAlert).toHaveBeenCalledTimes(1); expect(second).toHaveLength(0); }); }); @@ -278,9 +276,9 @@ describe("triggerNotifications with multiple providers", () => { regions: ["ams"], }); - assertSpyCalls(mockSlackSendAlert, 1); - assertSpyCalls(mockDiscordSendAlert, 1); - assertSpyCalls(mockEmailSendAlert, 0); + expect(mockSlackSendAlert).toHaveBeenCalledTimes(1); + expect(mockDiscordSendAlert).toHaveBeenCalledTimes(1); + expect(mockEmailSendAlert).toHaveBeenCalledTimes(0); expect(result).toHaveLength(2); expect(result).toContainEqual({ @@ -304,8 +302,8 @@ describe("triggerNotifications with multiple providers", () => { regions: ["ams"], }); - assertSpyCalls(mockSlackSendRecovery, 1); - assertSpyCalls(mockDiscordSendRecovery, 1); + expect(mockSlackSendRecovery).toHaveBeenCalledTimes(1); + expect(mockDiscordSendRecovery).toHaveBeenCalledTimes(1); expect(result).toHaveLength(2); const providers = result.map((r) => r.provider).sort(); diff --git a/apps/workflows/src/checker/outbox.test.ts b/apps/workflows/src/checker/outbox.test.ts index b9cce995d..f61c3f9d9 100644 --- a/apps/workflows/src/checker/outbox.test.ts +++ b/apps/workflows/src/checker/outbox.test.ts @@ -19,19 +19,19 @@ import { beforeEach, describe, expect, - type Stub, - stub, test, -} from "@openstatus/test-utils"; + vi, + type MockInstance, +} from "vitest"; -import { checkerAudit } from "../utils/audit-log"; +import { stubPublishAuditLog } from "../test/stub-audit-log"; import { drainOutbox, shutdownOutbox, sweepExpiredOutbox } from "./outbox"; import { providerToFunction } from "./utils"; -// Deno has no module mocking; stub the singleton the drainer resolves at call -// time, as alerting.test.ts does. +// Stub the singleton the drainer resolves at call time, as alerting.test.ts +// does. // biome-ignore lint/suspicious/noExplicitAny: heterogeneous provider stubs -type AnyStub = Stub; +type AnyStub = MockInstance; let stubs: AnyStub[] = []; let workspaceId: number; @@ -56,13 +56,11 @@ afterAll(async () => { beforeEach(() => { stubs = []; - stubs.push( - stub(checkerAudit, "publishAuditLog", () => Promise.resolve()) as AnyStub, - ); + stubs.push(stubPublishAuditLog()); }); afterEach(async () => { - for (const s of stubs) s.restore(); + for (const s of stubs) s.mockRestore(); stubs = []; await db .delete(notificationOutbox) @@ -119,7 +117,9 @@ async function insertOutboxRow(overrides: { describe("drainOutbox", () => { test("delivers a row, marks it done and records the send", async () => { stubs.push( - stub(providerToFunction.email, "sendAlert", () => Promise.resolve()), + vi + .spyOn(providerToFunction.email, "sendAlert") + .mockImplementation(() => Promise.resolve()), ); await insertOutboxRow({ cronTimestamp: Date.now() }); @@ -151,9 +151,9 @@ describe("drainOutbox", () => { test("dead-letters a row past its deadline and frees the channel", async () => { stubs.push( - stub(providerToFunction.email, "sendAlert", () => - Promise.reject(new Error("provider down")), - ), + vi + .spyOn(providerToFunction.email, "sendAlert") + .mockImplementation(() => Promise.reject(new Error("provider down"))), ); // `attempts` caps the backoff (>=15s), so the failed send's retry lands // past the deadline below without the claim racing a 1s deadline under @@ -190,11 +190,9 @@ describe("drainOutbox", () => { test("a hanging provider is bounded by the timeout", async () => { stubs.push( - stub( - providerToFunction.email, - "sendAlert", - () => new Promise(() => {}), - ), + vi + .spyOn(providerToFunction.email, "sendAlert") + .mockImplementation(() => new Promise(() => {})), ); await insertOutboxRow({ cronTimestamp: Date.now(), @@ -215,10 +213,14 @@ describe("drainOutbox", () => { test("claims only the oldest pending row per channel", async () => { stubs.push( - stub(providerToFunction.email, "sendAlert", () => Promise.resolve()), + vi + .spyOn(providerToFunction.email, "sendAlert") + .mockImplementation(() => Promise.resolve()), ); stubs.push( - stub(providerToFunction.email, "sendRecovery", () => Promise.resolve()), + vi + .spyOn(providerToFunction.email, "sendRecovery") + .mockImplementation(() => Promise.resolve()), ); const base = Date.now(); @@ -261,7 +263,9 @@ describe("drainOutbox", () => { test("an sms row over quota is recorded, not delivered", async () => { stubs.push( - stub(providerToFunction.sms, "sendAlert", () => Promise.resolve()), + vi + .spyOn(providerToFunction.sms, "sendAlert") + .mockImplementation(() => Promise.resolve()), ); const { workspace: smsWorkspace } = await createTestWorkspace({ @@ -329,9 +333,9 @@ describe("drainOutbox", () => { describe("retry", () => { test("a failed send is handed back with its backoff recorded", async () => { stubs.push( - stub(providerToFunction.email, "sendAlert", () => - Promise.reject(new Error("provider down")), - ), + vi + .spyOn(providerToFunction.email, "sendAlert") + .mockImplementation(() => Promise.reject(new Error("provider down"))), ); await insertOutboxRow({ cronTimestamp: Date.now() }); @@ -369,7 +373,9 @@ describe("retry", () => { test("a lapsed lease lets the other machine take the row over", async () => { stubs.push( - stub(providerToFunction.email, "sendAlert", () => Promise.resolve()), + vi + .spyOn(providerToFunction.email, "sendAlert") + .mockImplementation(() => Promise.resolve()), ); // A worker that died mid-attempt: still claimed, lease already expired. await insertOutboxRow({ @@ -398,7 +404,9 @@ describe("retry", () => { test("a live lease is left alone", async () => { stubs.push( - stub(providerToFunction.email, "sendAlert", () => Promise.resolve()), + vi + .spyOn(providerToFunction.email, "sendAlert") + .mockImplementation(() => Promise.resolve()), ); await insertOutboxRow({ cronTimestamp: Date.now(), @@ -433,7 +441,7 @@ describe("shutdownOutbox", () => { release = resolve; }); stubs.push( - stub(providerToFunction.email, "sendAlert", () => { + vi.spyOn(providerToFunction.email, "sendAlert").mockImplementation(() => { dispatched = true; return pending; }), @@ -476,7 +484,9 @@ describe("shutdownOutbox", () => { test("hands claimed work back instead of delivering it", async () => { stubs.push( - stub(providerToFunction.email, "sendAlert", () => Promise.resolve()), + vi + .spyOn(providerToFunction.email, "sendAlert") + .mockImplementation(() => Promise.resolve()), ); await insertOutboxRow({ cronTimestamp: Date.now() }); diff --git a/apps/workflows/src/checker/private-location.test.ts b/apps/workflows/src/checker/private-location.test.ts index 8da20e5fb..cd9efd607 100644 --- a/apps/workflows/src/checker/private-location.test.ts +++ b/apps/workflows/src/checker/private-location.test.ts @@ -12,23 +12,22 @@ import { createTestWorkspace } from "@openstatus/db/src/test/factories"; import { afterAll, afterEach, - assertSpyCalls, beforeAll, beforeEach, describe, expect, - type Stub, - stub, test, -} from "@openstatus/test-utils"; + vi, + type MockInstance, +} from "vitest"; import { env } from "../env"; -import { checkerAudit } from "../utils/audit-log"; +import { stubPublishAuditLog } from "../test/stub-audit-log"; import { checkerRoute } from "./index"; import { providerToFunction } from "./utils"; // biome-ignore lint/suspicious/noExplicitAny: heterogeneous provider stubs -type AnyStub = Stub; +type AnyStub = MockInstance; // Dedicated fixtures, not seed monitor 1: other suites (api/server maintenance // tests) put seed monitor 1 under active maintenance on the shared CI database, @@ -292,19 +291,19 @@ describe("updateStatusPrivate", () => { beforeEach(() => { stubs = []; stubs.push( - stub(checkerAudit, "publishAuditLog", () => + stubPublishAuditLog(() => Promise.resolve({ successful_rows: 1, quarantined_rows: 0 }), - ) as AnyStub, + ), ); - mockEmailSendAlert = stub(providerToFunction.email, "sendAlert", () => - Promise.resolve(), - ) as AnyStub; - mockEmailSendRecovery = stub(providerToFunction.email, "sendRecovery", () => - Promise.resolve(), - ) as AnyStub; - mockEmailSendDegraded = stub(providerToFunction.email, "sendDegraded", () => - Promise.resolve(), - ) as AnyStub; + mockEmailSendAlert = vi + .spyOn(providerToFunction.email, "sendAlert") + .mockImplementation(() => Promise.resolve()) as AnyStub; + mockEmailSendRecovery = vi + .spyOn(providerToFunction.email, "sendRecovery") + .mockImplementation(() => Promise.resolve()) as AnyStub; + mockEmailSendDegraded = vi + .spyOn(providerToFunction.email, "sendDegraded") + .mockImplementation(() => Promise.resolve()) as AnyStub; stubs.push( mockEmailSendAlert, mockEmailSendRecovery, @@ -313,7 +312,7 @@ describe("updateStatusPrivate", () => { }); afterEach(async () => { - for (const s of stubs) s.restore(); + for (const s of stubs) s.mockRestore(); stubs = []; await db .delete(privateLocationMonitorStatus) @@ -346,7 +345,7 @@ describe("updateStatusPrivate", () => { "Basic wrong-secret", ); expect(res.status).toBe(401); - assertSpyCalls(mockEmailSendAlert, 0); + expect(mockEmailSendAlert).toHaveBeenCalledTimes(0); }); test("rejects an invalid payload with 422", async () => { @@ -371,7 +370,7 @@ describe("updateStatusPrivate", () => { message: "down", }); expect(res.status).toBe(200); - assertSpyCalls(mockEmailSendAlert, 1); + expect(mockEmailSendAlert).toHaveBeenCalledTimes(1); const row = await readRow(TEST_MONITOR_ID, TEST_LOCATION_ID); expect(row?.status).toBe("error"); @@ -385,7 +384,7 @@ describe("updateStatusPrivate", () => { status: "error", cronTimestamp: 9300020, }); - assertSpyCalls(mockEmailSendAlert, 1); + expect(mockEmailSendAlert).toHaveBeenCalledTimes(1); const res = await post({ monitorId: String(TEST_MONITOR_ID), @@ -394,7 +393,7 @@ describe("updateStatusPrivate", () => { cronTimestamp: 9300021, }); expect(res.status).toBe(200); - assertSpyCalls(mockEmailSendAlert, 1); + expect(mockEmailSendAlert).toHaveBeenCalledTimes(1); const row = await readRow(TEST_MONITOR_ID, TEST_LOCATION_ID); expect(row?.cronTimestamp).toBe(9300021); @@ -407,7 +406,7 @@ describe("updateStatusPrivate", () => { status: "error", cronTimestamp: 9300030, }); - assertSpyCalls(mockEmailSendAlert, 1); + expect(mockEmailSendAlert).toHaveBeenCalledTimes(1); const res = await post({ monitorId: String(TEST_MONITOR_ID), @@ -416,7 +415,7 @@ describe("updateStatusPrivate", () => { cronTimestamp: 9300031, }); expect(res.status).toBe(200); - assertSpyCalls(mockEmailSendRecovery, 1); + expect(mockEmailSendRecovery).toHaveBeenCalledTimes(1); const row = await readRow(TEST_MONITOR_ID, TEST_LOCATION_ID); expect(row?.status).toBe("active"); @@ -431,7 +430,7 @@ describe("updateStatusPrivate", () => { latency: 5000, }); expect(res.status).toBe(200); - assertSpyCalls(mockEmailSendDegraded, 1); + expect(mockEmailSendDegraded).toHaveBeenCalledTimes(1); const row = await readRow(TEST_MONITOR_ID, TEST_LOCATION_ID); expect(row?.status).toBe("degraded"); @@ -452,7 +451,7 @@ describe("updateStatusPrivate", () => { cronTimestamp: 9300049, }); expect(res.status).toBe(200); - assertSpyCalls(mockEmailSendAlert, 0); + expect(mockEmailSendAlert).toHaveBeenCalledTimes(0); const row = await readRow(TEST_MONITOR_ID, TEST_LOCATION_ID); expect(row?.status).toBe("active"); @@ -467,7 +466,7 @@ describe("updateStatusPrivate", () => { cronTimestamp: 9300060, }); expect(res.status).toBe(200); - assertSpyCalls(mockEmailSendAlert, 0); + expect(mockEmailSendAlert).toHaveBeenCalledTimes(0); const row = await readRow(TEST_MONITOR_ID, UNATTACHED_LOCATION_ID); expect(row).toBeUndefined(); @@ -481,7 +480,7 @@ describe("updateStatusPrivate", () => { cronTimestamp: 9300070, }); expect(res.status).toBe(200); - assertSpyCalls(mockEmailSendAlert, 0); + expect(mockEmailSendAlert).toHaveBeenCalledTimes(0); }); test("private-only monitor: updates status when threshold met (2/3 locations agree)", async () => { @@ -520,7 +519,7 @@ describe("updateStatusPrivate", () => { .where(eq(monitor.id, PRIVATE_ONLY_MONITOR_ID)) .get(); expect(monitorAfter2?.status).toBe("error"); - assertSpyCalls(mockEmailSendAlert, 1); + expect(mockEmailSendAlert).toHaveBeenCalledTimes(1); }); test("does not change monitor.status from error when 1/3 locations report degraded (threshold not met)", async () => { diff --git a/apps/workflows/src/checker/transition.test.ts b/apps/workflows/src/checker/transition.test.ts index ec616a4fd..9b6459e5d 100644 --- a/apps/workflows/src/checker/transition.test.ts +++ b/apps/workflows/src/checker/transition.test.ts @@ -12,13 +12,7 @@ import { createTestWorkspace, linkNotificationToMonitor, } from "@openstatus/db/src/test/factories"; -import { - afterAll, - beforeAll, - describe, - expect, - test, -} from "@openstatus/test-utils"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { drainOutbox } from "./outbox"; import { applyStatusTransition, isStaleCheck } from "./transition"; diff --git a/apps/workflows/src/checker/update-status.test.ts b/apps/workflows/src/checker/update-status.test.ts index 9ab676ccc..c845a8dd3 100644 --- a/apps/workflows/src/checker/update-status.test.ts +++ b/apps/workflows/src/checker/update-status.test.ts @@ -14,23 +14,22 @@ import { import { afterAll, afterEach, - assertSpyCalls, beforeAll, beforeEach, describe, expect, - type Stub, - stub, test, -} from "@openstatus/test-utils"; + vi, + type MockInstance, +} from "vitest"; import { env } from "../env"; -import { checkerAudit } from "../utils/audit-log"; +import { stubPublishAuditLog } from "../test/stub-audit-log"; import { checkerRoute } from "./index"; import { providerToFunction } from "./utils"; // biome-ignore lint/suspicious/noExplicitAny: heterogeneous provider stubs -type AnyStub = Stub; +type AnyStub = MockInstance; let stubs: AnyStub[] = []; let workspaceId: number; @@ -61,7 +60,7 @@ beforeEach(() => { }); afterEach(async () => { - for (const s of stubs) s.restore(); + for (const s of stubs) s.mockRestore(); stubs = []; await db .delete(notificationTrigger) @@ -98,13 +97,13 @@ describe("updateStatus audit failures", () => { // Tinybird is down: every audit publish rejects, including the status one // that runs after the transition batch has committed. stubs.push( - stub(checkerAudit, "publishAuditLog", () => + stubPublishAuditLog(() => Promise.reject(new Error("tinybird unavailable")), - ) as AnyStub, - ); - const sendAlert = stub(providerToFunction.email, "sendAlert", () => - Promise.resolve(), + ), ); + const sendAlert = vi + .spyOn(providerToFunction.email, "sendAlert") + .mockImplementation(() => Promise.resolve()); stubs.push(sendAlert as AnyStub); const res = await post({ @@ -118,7 +117,7 @@ describe("updateStatus audit failures", () => { // Retrying this transition is useless — the region status is already // written, so the retry takes the fast path and sends nothing. expect(res.status).toBe(200); - assertSpyCalls(sendAlert, 1); + expect(sendAlert).toHaveBeenCalledTimes(1); const rows = await db .select() diff --git a/apps/workflows/src/cron/external-status-detect.test.ts b/apps/workflows/src/cron/external-status-detect.test.ts index bdb63cf00..00325a975 100644 --- a/apps/workflows/src/cron/external-status-detect.test.ts +++ b/apps/workflows/src/cron/external-status-detect.test.ts @@ -1,6 +1,6 @@ import { FetchError } from "@openstatus/status-fetcher"; import type { DetectionResult } from "@openstatus/status-fetcher"; -import { describe, expect, test } from "@openstatus/test-utils"; +import { describe, expect, test } from "vitest"; import { PROBE_TTL_MS, diff --git a/apps/workflows/src/cron/private-location-health.test.ts b/apps/workflows/src/cron/private-location-health.test.ts index d717b51b5..4c219e3d9 100644 --- a/apps/workflows/src/cron/private-location-health.test.ts +++ b/apps/workflows/src/cron/private-location-health.test.ts @@ -11,15 +11,14 @@ import { } from "@openstatus/db/src/test/factories"; import { afterEach, - assertSpyCalls, beforeAll, beforeEach, describe, expect, - type Stub, - stub, test, -} from "@openstatus/test-utils"; + vi, + type MockInstance, +} from "vitest"; import { emailClient, @@ -34,7 +33,7 @@ const FRESH = new Date(NOW.getTime() - 60 * 1000); const STALE = new Date(NOW.getTime() - STALE_THRESHOLD_MS - 60 * 1000); // biome-ignore lint/suspicious/noExplicitAny: email stub -type AnyStub = Stub; +type AnyStub = MockInstance; async function seedLocation( status: "active" | "error", @@ -73,13 +72,13 @@ describe("runPrivateLocationHealth", () => { }); beforeEach(() => { - mockEmail = stub(emailClient, "sendPrivateLocationAlert", () => - Promise.resolve(), - ) as AnyStub; + mockEmail = vi + .spyOn(emailClient, "sendPrivateLocationAlert") + .mockImplementation(() => Promise.resolve()) as AnyStub; }); afterEach(async () => { - mockEmail.restore(); + mockEmail.mockRestore(); await db .delete(auditLog) .where(eq(auditLog.entityId, String(TEST_LOCATION_ID))); @@ -94,12 +93,12 @@ describe("runPrivateLocationHealth", () => { await runPrivateLocationHealth(NOW); expect((await readStatus())?.status).toBe("error"); - assertSpyCalls(mockEmail, 1); - expect(mockEmail.calls[0].args[0].status).toBe("error"); - expect(mockEmail.calls[0].args[0].locationName).toBe( + expect(mockEmail).toHaveBeenCalledTimes(1); + expect(mockEmail.mock.calls[0][0].status).toBe("error"); + expect(mockEmail.mock.calls[0][0].locationName).toBe( "health-test-location", ); - expect(mockEmail.calls[0].args[0].monitorCount).toBe(0); + expect(mockEmail.mock.calls[0][0].monitorCount).toBe(0); }); test("passes the number of monitors scheduled on the location", async () => { @@ -122,7 +121,7 @@ describe("runPrivateLocationHealth", () => { try { await runPrivateLocationHealth(NOW); - expect(mockEmail.calls[0].args[0].monitorCount).toBe(1); + expect(mockEmail.mock.calls[0][0].monitorCount).toBe(1); } finally { await db.delete(monitor).where(eq(monitor.id, linked.id)); await db.delete(monitor).where(eq(monitor.id, unlinked.id)); @@ -136,8 +135,8 @@ describe("runPrivateLocationHealth", () => { await runPrivateLocationHealth(NOW); expect((await readStatus())?.status).toBe("active"); - assertSpyCalls(mockEmail, 1); - expect(mockEmail.calls[0].args[0].status).toBe("recovered"); + expect(mockEmail).toHaveBeenCalledTimes(1); + expect(mockEmail.mock.calls[0][0].status).toBe("recovered"); }); test("active + fresh → untouched, no email", async () => { @@ -146,7 +145,7 @@ describe("runPrivateLocationHealth", () => { await runPrivateLocationHealth(NOW); expect((await readStatus())?.status).toBe("active"); - assertSpyCalls(mockEmail, 0); + expect(mockEmail).toHaveBeenCalledTimes(0); }); test("error + stale → untouched, no email", async () => { @@ -155,7 +154,7 @@ describe("runPrivateLocationHealth", () => { await runPrivateLocationHealth(NOW); expect((await readStatus())?.status).toBe("error"); - assertSpyCalls(mockEmail, 0); + expect(mockEmail).toHaveBeenCalledTimes(0); }); test("lastSeenAt NULL → skipped regardless of status", async () => { @@ -164,6 +163,6 @@ describe("runPrivateLocationHealth", () => { await runPrivateLocationHealth(NOW); expect((await readStatus())?.status).toBe("error"); - assertSpyCalls(mockEmail, 0); + expect(mockEmail).toHaveBeenCalledTimes(0); }); }); diff --git a/apps/workflows/src/cron/scheduler.test.ts b/apps/workflows/src/cron/scheduler.test.ts index 517e7c367..356213103 100644 --- a/apps/workflows/src/cron/scheduler.test.ts +++ b/apps/workflows/src/cron/scheduler.test.ts @@ -1,5 +1,5 @@ -import { describe, expect, test } from "@openstatus/test-utils"; import { Cron, Result } from "effect"; +import { describe, expect, test } from "vitest"; import { SCHEDULED_TASKS } from "./scheduler"; diff --git a/apps/workflows/src/cron/status-drift.test.ts b/apps/workflows/src/cron/status-drift.test.ts index 4b72744c3..5cdea3f90 100644 --- a/apps/workflows/src/cron/status-drift.test.ts +++ b/apps/workflows/src/cron/status-drift.test.ts @@ -18,18 +18,18 @@ import { beforeEach, describe, expect, - type Stub, - stub, test, -} from "@openstatus/test-utils"; + vi, + type MockInstance, +} from "vitest"; import { applyStatusTransition } from "../checker/transition"; import { providerToFunction } from "../checker/utils"; -import { checkerAudit } from "../utils/audit-log"; +import { stubPublishAuditLog } from "../test/stub-audit-log"; import { handleStatusDriftCron } from "./status-drift"; // biome-ignore lint/suspicious/noExplicitAny: heterogeneous provider stubs -type AnyStub = Stub; +type AnyStub = MockInstance; let stubs: AnyStub[] = []; let workspaceId: number; @@ -40,16 +40,16 @@ beforeAll(async () => { beforeEach(() => { stubs = []; + stubs.push(stubPublishAuditLog()); stubs.push( - stub(checkerAudit, "publishAuditLog", () => Promise.resolve()) as AnyStub, - ); - stubs.push( - stub(providerToFunction.email, "sendAlert", () => Promise.resolve()), + vi + .spyOn(providerToFunction.email, "sendAlert") + .mockImplementation(() => Promise.resolve()), ); }); afterEach(() => { - for (const s of stubs) s.restore(); + for (const s of stubs) s.mockRestore(); stubs = []; }); @@ -157,10 +157,10 @@ describe("handleStatusDriftCron", () => { describe("drift repair delivery", () => { test("sends the notification the repair is recovering", async () => { const sent: string[] = []; - for (const s of stubs) s.restore(); + for (const s of stubs) s.mockRestore(); stubs = [ - stub(checkerAudit, "publishAuditLog", () => Promise.resolve()) as AnyStub, - stub(providerToFunction.email, "sendAlert", () => { + stubPublishAuditLog(), + vi.spyOn(providerToFunction.email, "sendAlert").mockImplementation(() => { sent.push("alert"); return Promise.resolve(); }), diff --git a/apps/workflows/src/test/stub-audit-log.ts b/apps/workflows/src/test/stub-audit-log.ts new file mode 100644 index 000000000..d986d1ce4 --- /dev/null +++ b/apps/workflows/src/test/stub-audit-log.ts @@ -0,0 +1,21 @@ +import { type MockInstance, vi } from "vitest"; + +import { checkerAudit } from "../utils/audit-log"; + +type Publish = typeof checkerAudit.publishAuditLog; + +/** + * `publishAuditLog` is a getter that builds the ingest function on each read, + * so it has to be spied as an accessor. vitest only types the `"get"` form for + * non-function properties; the key is widened here, safe because the getter + * really exists on the instance's prototype. + */ +export function stubPublishAuditLog( + impl: Publish = () => + Promise.resolve({ successful_rows: 0, quarantined_rows: 0 }), +): MockInstance { + const target = checkerAudit as unknown as { publishAuditLog: unknown }; + return vi + .spyOn(target, "publishAuditLog", "get") + .mockReturnValue(vi.fn(impl)); +} diff --git a/apps/workflows/tsconfig.json b/apps/workflows/tsconfig.json index 455f0cda3..25e8f654b 100644 --- a/apps/workflows/tsconfig.json +++ b/apps/workflows/tsconfig.json @@ -4,14 +4,5 @@ "module": "ESNext", "target": "ESNext", "moduleResolution": "bundler" - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/apps/workflows/vitest.config.ts b/apps/workflows/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/apps/workflows/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/ai/package.json b/packages/ai/package.json index dea32ab20..9a7997ff2 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -13,7 +13,7 @@ }, "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", + "test": "vitest run", "lint": "oxlint", "tsc": "tsc --noEmit" }, @@ -23,10 +23,10 @@ }, "devDependencies": { "@openstatus/db": "workspace:*", + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/ai/src/resolve-model.test.ts b/packages/ai/src/resolve-model.test.ts index c9d7c6921..be75961eb 100644 --- a/packages/ai/src/resolve-model.test.ts +++ b/packages/ai/src/resolve-model.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; +import { afterEach, beforeEach, describe, expect, test } from "vitest"; import { resolveChatModel } from "./resolve-model"; diff --git a/packages/ai/tsconfig.json b/packages/ai/tsconfig.json index cbf5122bf..230005c38 100644 --- a/packages/ai/tsconfig.json +++ b/packages/ai/tsconfig.json @@ -3,14 +3,5 @@ "include": ["src", "*.ts"], "compilerOptions": { "noEmit": true - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/ai/vitest.config.ts b/packages/ai/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/ai/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/analytics/tsconfig.json b/packages/analytics/tsconfig.json index 7c76dbe70..4b64f988a 100644 --- a/packages/analytics/tsconfig.json +++ b/packages/analytics/tsconfig.json @@ -1,16 +1,7 @@ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@openstatus/tsconfig/base.json", - "exclude": [ - "dist", - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ], + "exclude": ["dist", "node_modules"], "compilerOptions": { "outDir": "dist", "target": "es2021", diff --git a/packages/api/package.json b/packages/api/package.json index d42aff56c..1a8299812 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -12,7 +12,7 @@ }, "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports --env-file=.env.test --import-map=test.importmap.json", + "test": "vitest run", "env": "node env.ts" }, "dependencies": { @@ -66,11 +66,11 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/react": "catalog:", "@types/react-dom": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/api/src/router/blob.test.ts b/packages/api/src/router/blob.test.ts index f8498a1e3..37ac7da88 100644 --- a/packages/api/src/router/blob.test.ts +++ b/packages/api/src/router/blob.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { isSvgFile, sanitizeSvg } from "./blob"; diff --git a/packages/api/src/router/checker.test.ts b/packages/api/src/router/checker.test.ts index 4ab6e3afa..c90429215 100644 --- a/packages/api/src/router/checker.test.ts +++ b/packages/api/src/router/checker.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { afterEach, describe, test } from "@std/testing/bdd"; +import { afterEach, describe, expect, test } from "vitest"; import { testGrpc, testHttp, testTcp } from "./checker"; diff --git a/packages/api/src/router/domain.test.ts b/packages/api/src/router/domain.test.ts index 10ee4cb3f..d8cad6a95 100644 --- a/packages/api/src/router/domain.test.ts +++ b/packages/api/src/router/domain.test.ts @@ -4,9 +4,8 @@ import { createPage, createTestWorkspace, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, test } from "@std/testing/bdd"; import { TRPCError } from "@trpc/server"; +import { afterAll, beforeAll, expect, test } from "vitest"; import { edgeRouter } from "../edge"; import { vercelFetch } from "../lib/vercel"; diff --git a/packages/api/src/router/effective-status.test.ts b/packages/api/src/router/effective-status.test.ts index 9c7ef9e86..a184fed57 100644 --- a/packages/api/src/router/effective-status.test.ts +++ b/packages/api/src/router/effective-status.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { computeEffectiveStatus } from "./effective-status"; diff --git a/packages/api/src/router/import.test.ts b/packages/api/src/router/import.test.ts index 40d9ac0ce..352d07482 100644 --- a/packages/api/src/router/import.test.ts +++ b/packages/api/src/router/import.test.ts @@ -23,8 +23,7 @@ import { MOCK_SUBSCRIBERS, } from "@openstatus/importers/statuspage/fixtures"; import { clearAuditLogFor } from "@openstatus/services/test/helpers"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, expect, test } from "vitest"; import { edgeRouter } from "../edge"; import { createInnerTRPCContext } from "../trpc"; diff --git a/packages/api/src/router/incident.test.ts b/packages/api/src/router/incident.test.ts index b464617cf..a67a10a5c 100644 --- a/packages/api/src/router/incident.test.ts +++ b/packages/api/src/router/incident.test.ts @@ -2,9 +2,8 @@ import { db, eq, inArray } from "@openstatus/db"; import { incident, incidentEvent } from "@openstatus/db/src/schema"; import { createIncident } from "@openstatus/db/src/test/factories"; import { clearAuditLogFor } from "@openstatus/services/test/helpers"; -import { expect } from "@std/expect"; -import { afterAll, test } from "@std/testing/bdd"; import { TRPCError } from "@trpc/server"; +import { afterAll, expect, test } from "vitest"; import { edgeRouter } from "../edge"; import { createInnerTRPCContext } from "../trpc"; diff --git a/packages/api/src/router/maintenance.test.ts b/packages/api/src/router/maintenance.test.ts index 52b5ffa79..daca86524 100644 --- a/packages/api/src/router/maintenance.test.ts +++ b/packages/api/src/router/maintenance.test.ts @@ -7,9 +7,8 @@ import { pageComponent, } from "@openstatus/db/src/schema"; import { clearAuditLogFor } from "@openstatus/services/test/helpers"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, test } from "@std/testing/bdd"; import { TRPCError } from "@trpc/server"; +import { afterAll, beforeAll, expect, test } from "vitest"; import { edgeRouter } from "../edge"; import { createInnerTRPCContext } from "../trpc"; diff --git a/packages/api/src/router/monitor.test.ts b/packages/api/src/router/monitor.test.ts index 62079b222..9148f4c25 100644 --- a/packages/api/src/router/monitor.test.ts +++ b/packages/api/src/router/monitor.test.ts @@ -13,9 +13,8 @@ import { createMonitor, createWorkspace, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, test } from "@std/testing/bdd"; import { TRPCError } from "@trpc/server"; +import { afterAll, beforeAll, expect, test } from "vitest"; import { edgeRouter } from "../edge"; import { createInnerTRPCContext } from "../trpc"; diff --git a/packages/api/src/router/notification.test.ts b/packages/api/src/router/notification.test.ts index a0ef37bf0..7137e5db4 100644 --- a/packages/api/src/router/notification.test.ts +++ b/packages/api/src/router/notification.test.ts @@ -3,9 +3,8 @@ import { notification, notificationsToMonitors, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, test } from "@std/testing/bdd"; import { TRPCError } from "@trpc/server"; +import { afterAll, beforeAll, expect, test } from "vitest"; import { edgeRouter } from "../edge"; import { createInnerTRPCContext } from "../trpc"; diff --git a/packages/api/src/router/oauth.test.ts b/packages/api/src/router/oauth.test.ts index cc88a3ef0..fe65cdef6 100644 --- a/packages/api/src/router/oauth.test.ts +++ b/packages/api/src/router/oauth.test.ts @@ -12,9 +12,8 @@ import { verifyAccessToken, } from "@openstatus/services/oauth"; import { clearAuditLogFor } from "@openstatus/services/test/helpers"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; import { NextRequest } from "next/server.js"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { edgeRouter } from "../edge"; import { createInnerTRPCContext, createTRPCContext } from "../trpc"; diff --git a/packages/api/src/router/pageComponent.test.ts b/packages/api/src/router/pageComponent.test.ts index 187cef84c..a874351c5 100644 --- a/packages/api/src/router/pageComponent.test.ts +++ b/packages/api/src/router/pageComponent.test.ts @@ -1,9 +1,8 @@ import { db, eq } from "@openstatus/db"; import { monitor } from "@openstatus/db/src/schema"; import { allPlans } from "@openstatus/db/src/schema/plan/config"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, test } from "@std/testing/bdd"; import { TRPCError } from "@trpc/server"; +import { afterAll, beforeAll, expect, test } from "vitest"; import { edgeRouter } from "../edge"; import { createInnerTRPCContext } from "../trpc"; diff --git a/packages/api/src/router/privateLocation.test.ts b/packages/api/src/router/privateLocation.test.ts index 9c0737d21..186771ae0 100644 --- a/packages/api/src/router/privateLocation.test.ts +++ b/packages/api/src/router/privateLocation.test.ts @@ -8,9 +8,8 @@ import { createMonitor, createWorkspace, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, test } from "@std/testing/bdd"; import { TRPCError } from "@trpc/server"; +import { afterAll, beforeAll, expect, test } from "vitest"; import { edgeRouter } from "../edge"; import { createInnerTRPCContext } from "../trpc"; diff --git a/packages/api/src/router/slackUser.test.ts b/packages/api/src/router/slackUser.test.ts index 0d6051759..9fe031d0a 100644 --- a/packages/api/src/router/slackUser.test.ts +++ b/packages/api/src/router/slackUser.test.ts @@ -6,9 +6,8 @@ import { createWorkspace, } from "@openstatus/db/src/test/factories"; import { signSlackLinkToken } from "@openstatus/services/slack-user"; -import { expect } from "@std/expect"; -import { beforeAll, test } from "@std/testing/bdd"; import { TRPCError } from "@trpc/server"; +import { beforeAll, expect, test } from "vitest"; import { lambdaRouter } from "../lambda"; import { createInnerTRPCContext } from "../trpc"; diff --git a/packages/api/src/router/statusPage.access.test.ts b/packages/api/src/router/statusPage.access.test.ts index 4376987fc..dada265aa 100644 --- a/packages/api/src/router/statusPage.access.test.ts +++ b/packages/api/src/router/statusPage.access.test.ts @@ -5,11 +5,10 @@ import { createPageComponent, createTestWorkspace, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; import { TRPCError } from "@trpc/server"; import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; import { NextRequest } from "next/server.js"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { appRouter } from "../root"; import { createInnerTRPCContext, createTRPCContext } from "../trpc"; diff --git a/packages/api/src/router/statusPage.e2e.test.ts b/packages/api/src/router/statusPage.e2e.test.ts index dfbb340c0..5eb68d45c 100644 --- a/packages/api/src/router/statusPage.e2e.test.ts +++ b/packages/api/src/router/statusPage.e2e.test.ts @@ -7,8 +7,7 @@ import { pageSubscriber, workspace, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; /** * End-to-end integration tests for the full unsubscribe flow. diff --git a/packages/api/src/router/statusPage.unsubscribe.test.ts b/packages/api/src/router/statusPage.unsubscribe.test.ts index 8cb66d5f6..3ff5b5490 100644 --- a/packages/api/src/router/statusPage.unsubscribe.test.ts +++ b/packages/api/src/router/statusPage.unsubscribe.test.ts @@ -1,7 +1,6 @@ import { db, eq } from "@openstatus/db"; import { page, pageSubscriber } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; // Test data setup let testPageId: number; diff --git a/packages/api/src/router/statusPage.utils.test.ts b/packages/api/src/router/statusPage.utils.test.ts index 30a7ff249..08d5245e9 100644 --- a/packages/api/src/router/statusPage.utils.test.ts +++ b/packages/api/src/router/statusPage.utils.test.ts @@ -5,8 +5,7 @@ import type { StatusReport, StatusReportUpdate, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { currentImpactByComponent, @@ -2105,8 +2104,8 @@ describe("withTinybirdFallback", () => { }); it("falls back when the read exceeds the timeout", async () => { - // keep a handle on the slow read's timer — deno's leak sanitizer fails - // the test if it outlives the assertion + // keep a handle on the slow read's timer so it can be cleared before + // the test ends let slowRead!: ReturnType; const result = await withTinybirdFallback( () => diff --git a/packages/api/src/router/statusReport.test.ts b/packages/api/src/router/statusReport.test.ts index a0cb8c7b1..6dd6e05d0 100644 --- a/packages/api/src/router/statusReport.test.ts +++ b/packages/api/src/router/statusReport.test.ts @@ -7,9 +7,8 @@ import { statusReportsToPageComponents, } from "@openstatus/db/src/schema"; import { clearAuditLogFor } from "@openstatus/services/test/helpers"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, test } from "@std/testing/bdd"; import { TRPCError } from "@trpc/server"; +import { afterAll, beforeAll, expect, test } from "vitest"; import { edgeRouter } from "../edge"; import { createInnerTRPCContext } from "../trpc"; diff --git a/packages/api/src/router/stripe/trial-lifecycle.test.ts b/packages/api/src/router/stripe/trial-lifecycle.test.ts index e919d2fbf..8dc54e708 100644 --- a/packages/api/src/router/stripe/trial-lifecycle.test.ts +++ b/packages/api/src/router/stripe/trial-lifecycle.test.ts @@ -12,10 +12,16 @@ import { createWorkspace, } from "@openstatus/db/src/test/factories"; import { delivery, resend } from "@openstatus/emails/src/send"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, type Stub, stub } from "@std/testing/mock"; import type Stripe from "stripe"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { edgeRouter } from "../../edge"; import { createInnerTRPCContext } from "../../trpc"; @@ -118,67 +124,71 @@ function asCaller(fixture: Awaited>) { describe("trial lifecycle", () => { let live: Stripe.Subscription[]; let customer: Stripe.Customer; - let stubs: Stub[]; - let updateSubscription: Stub; - let cancelStripeSubscription: Stub; - let createSession: Stub; + let stubs: MockInstance[]; + let updateSubscription: MockInstance; + let cancelStripeSubscription: MockInstance; + let createSession: MockInstance; beforeEach(() => { live = []; customer = customerWith(null); - updateSubscription = stub( - stripe.subscriptions, - "update", - (id: string, params?: Stripe.SubscriptionUpdateParams) => { - const base = live.find((s) => s.id === id) ?? subscription("cus_x"); - const items = params?.items?.[0]?.price - ? { - ...base.items, - data: [ - { - ...base.items.data[0], - price: { id: params.items[0].price }, - } as Stripe.SubscriptionItem, - ], - } - : base.items; - return Promise.resolve({ - ...base, - items, - status: params?.trial_end === "now" ? "active" : base.status, - trial_end: params?.trial_end === "now" ? null : base.trial_end, - } as Stripe.Response); - }, - ); - cancelStripeSubscription = stub(stripe.subscriptions, "cancel", () => - Promise.resolve({} as Stripe.Response), - ); - createSession = stub(stripe.checkout.sessions, "create", () => - Promise.resolve({ - url: "https://checkout.stripe.test/session", - } as Stripe.Response), - ); + updateSubscription = vi + .spyOn(stripe.subscriptions, "update") + .mockImplementation( + (id: string, params?: Stripe.SubscriptionUpdateParams) => { + const base = live.find((s) => s.id === id) ?? subscription("cus_x"); + const items = params?.items?.[0]?.price + ? { + ...base.items, + data: [ + { + ...base.items.data[0], + price: { id: params.items[0].price }, + } as Stripe.SubscriptionItem, + ], + } + : base.items; + return Promise.resolve({ + ...base, + items, + status: params?.trial_end === "now" ? "active" : base.status, + trial_end: params?.trial_end === "now" ? null : base.trial_end, + } as Stripe.Response); + }, + ); + cancelStripeSubscription = vi + .spyOn(stripe.subscriptions, "cancel") + .mockImplementation(() => + Promise.resolve({} as Stripe.Response), + ); + createSession = vi + .spyOn(stripe.checkout.sessions, "create") + .mockImplementation(() => + Promise.resolve({ + url: "https://checkout.stripe.test/session", + } as Stripe.Response), + ); stubs = [ updateSubscription, cancelStripeSubscription, createSession, - stub( - stripe.subscriptions, - "list", + vi.spyOn(stripe.subscriptions, "list").mockImplementation( () => ({ autoPagingToArray: (_opts: { limit: number }) => Promise.resolve(live), }) as Stripe.ApiListPromise, ), - stub(stripe.customers, "retrieve", () => - Promise.resolve(customer as Stripe.Response), - ), + vi + .spyOn(stripe.customers, "retrieve") + .mockImplementation(() => + Promise.resolve(customer as Stripe.Response), + ), ]; }); afterEach(() => { - for (const s of stubs) s.restore(); + for (const s of stubs) s.mockRestore(); }); function withCard() { @@ -199,8 +209,8 @@ describe("trial lifecycle", () => { }); expect(result?.type).toBe("checkout"); - assertSpyCalls(updateSubscription, 0); - expect(createSession.calls[0]?.args[0]).toMatchObject({ + expect(updateSubscription).toHaveBeenCalledTimes(0); + expect(createSession.mock.calls[0]?.[0]).toMatchObject({ mode: "subscription", customer: s.stripeId, line_items: [{ price: TEAM_PRICE, quantity: 1 }], @@ -221,7 +231,7 @@ describe("trial lifecycle", () => { }); expect(result?.type).toBe("setup"); - expect(createSession.calls[0]?.args[0]).toMatchObject({ + expect(createSession.mock.calls[0]?.[0]).toMatchObject({ mode: "setup", customer: s.stripeId, setup_intent_data: { metadata: { subscriptionId: sub.id } }, @@ -243,7 +253,7 @@ describe("trial lifecycle", () => { }); expect(result?.type).toBe("updated"); - expect(updateSubscription.calls[0]?.args).toEqual([ + expect(updateSubscription.mock.calls[0]).toEqual([ sub.id, { items: [{ id: "si_plan", price: TEAM_PRICE }], @@ -271,7 +281,7 @@ describe("trial lifecycle", () => { value: true, }), ).rejects.toMatchObject({ code: "PRECONDITION_FAILED" }); - assertSpyCalls(updateSubscription, 0); + expect(updateSubscription).toHaveBeenCalledTimes(0); }); test("trialing with a card → trial ended before the addon is added", async () => { @@ -280,19 +290,21 @@ describe("trial lifecycle", () => { const sub = subscription(s.stripeId); live = [sub]; const planItem = sub.items.data[0]; - const listItems = stub( - stripe.subscriptionItems, - "list", - () => - Promise.resolve({ - data: planItem ? [planItem] : [], - } as Stripe.Response< - Stripe.ApiList - >) as Stripe.ApiListPromise, - ); - const createItem = stub(stripe.subscriptionItems, "create", () => - Promise.resolve({} as Stripe.Response), - ); + const listItems = vi + .spyOn(stripe.subscriptionItems, "list") + .mockImplementation( + () => + Promise.resolve({ + data: planItem ? [planItem] : [], + } as Stripe.Response< + Stripe.ApiList + >) as Stripe.ApiListPromise, + ); + const createItem = vi + .spyOn(stripe.subscriptionItems, "create") + .mockImplementation(() => + Promise.resolve({} as Stripe.Response), + ); stubs.push(listItems, createItem); await stripeRouter.createCaller(asCaller(s)).addAddon({ @@ -301,11 +313,11 @@ describe("trial lifecycle", () => { value: true, }); - expect(updateSubscription.calls[0]?.args).toEqual([ + expect(updateSubscription.mock.calls[0]).toEqual([ sub.id, { trial_end: "now", payment_behavior: "error_if_incomplete" }, ]); - expect(createItem.calls[0]?.args[0]).toMatchObject({ + expect(createItem.mock.calls[0]?.[0]).toMatchObject({ price: WHITE_LABEL_PRICE, subscription: sub.id, }); @@ -318,9 +330,13 @@ describe("trial lifecycle", () => { const s = await seedTrial(); withCard(); live = [subscription(s.stripeId)]; - const delItem = stub(stripe.subscriptionItems, "del", () => - Promise.resolve({} as Stripe.Response), - ); + const delItem = vi + .spyOn(stripe.subscriptionItems, "del") + .mockImplementation(() => + Promise.resolve( + {} as Stripe.Response, + ), + ); stubs.push(delItem); await expect( @@ -330,8 +346,8 @@ describe("trial lifecycle", () => { value: false, }), ).rejects.toMatchObject({ code: "BAD_REQUEST" }); - assertSpyCalls(delItem, 0); - assertSpyCalls(updateSubscription, 0); + expect(delItem).toHaveBeenCalledTimes(0); + expect(updateSubscription).toHaveBeenCalledTimes(0); }); }); @@ -342,8 +358,8 @@ describe("trial lifecycle", () => { await cancelSubscription("cus_cancel_trial"); - expect(cancelStripeSubscription.calls[0]?.args[0]).toBe(sub.id); - assertSpyCalls(updateSubscription, 0); + expect(cancelStripeSubscription.mock.calls[0]?.[0]).toBe(sub.id); + expect(updateSubscription).toHaveBeenCalledTimes(0); }); test("active → cancelled at period end", async () => { @@ -352,8 +368,8 @@ describe("trial lifecycle", () => { await cancelSubscription("cus_cancel_active"); - assertSpyCalls(cancelStripeSubscription, 0); - expect(updateSubscription.calls[0]?.args[1]).toMatchObject({ + expect(cancelStripeSubscription).toHaveBeenCalledTimes(0); + expect(updateSubscription.mock.calls[0]?.[1]).toMatchObject({ cancel_at_period_end: true, }); }); @@ -367,7 +383,7 @@ describe("trial lifecycle", () => { await edgeRouter.createCaller(asCaller(s)).user.deleteAccount(); - expect(cancelStripeSubscription.calls[0]?.args[0]).toBe(sub.id); + expect(cancelStripeSubscription.mock.calls[0]?.[0]).toBe(sub.id); const after = await readWorkspace(s.ws.id); expect(after?.plan).toBe("free"); expect(after?.trialEndsAt).toBeNull(); @@ -390,7 +406,7 @@ describe("trial lifecycle", () => { edgeRouter.createCaller(asCaller(s)).user.deleteAccount(), ).rejects.toMatchObject({ code: "PRECONDITION_FAILED" }); - assertSpyCalls(cancelStripeSubscription, 0); + expect(cancelStripeSubscription).toHaveBeenCalledTimes(0); const after = await readWorkspace(s.ws.id); expect(after?.plan).toBe("starter"); expect(after?.trialEndsAt).toEqual(s.trialEndsAt); @@ -421,19 +437,25 @@ describe("trial lifecycle", () => { payment_method: "pm_new", metadata: { subscriptionId: "sub_setup" }, }; - const retrieveIntent = stub(stripe.setupIntents, "retrieve", () => - Promise.resolve(intent as Stripe.Response), - ); - const retrieveSub = stub(stripe.subscriptions, "retrieve", () => - Promise.resolve( - subscription("cus_setup", { - id: "sub_setup", - }) as Stripe.Response, - ), - ); - const updateCustomer = stub(stripe.customers, "update", () => - Promise.resolve({} as Stripe.Response), - ); + const retrieveIntent = vi + .spyOn(stripe.setupIntents, "retrieve") + .mockImplementation(() => + Promise.resolve(intent as Stripe.Response), + ); + const retrieveSub = vi + .spyOn(stripe.subscriptions, "retrieve") + .mockImplementation(() => + Promise.resolve( + subscription("cus_setup", { + id: "sub_setup", + }) as Stripe.Response, + ), + ); + const updateCustomer = vi + .spyOn(stripe.customers, "update") + .mockImplementation(() => + Promise.resolve({} as Stripe.Response), + ); stubs.push(retrieveIntent, retrieveSub, updateCustomer); await caller().sessionCompleted( @@ -444,11 +466,11 @@ describe("trial lifecycle", () => { } as Stripe.Checkout.Session), ); - expect(updateCustomer.calls[0]?.args).toEqual([ + expect(updateCustomer.mock.calls[0]).toEqual([ "cus_setup", { invoice_settings: { default_payment_method: "pm_new" } }, ]); - expect(updateSubscription.calls[0]?.args).toEqual([ + expect(updateSubscription.mock.calls[0]).toEqual([ "sub_setup", { default_payment_method: "pm_new" }, ]); @@ -482,8 +504,12 @@ describe("trial lifecycle", () => { }); test("a trial that ran out is audited as trial_ended, not churn", async () => { - const send = stub(resend.emails, "send"); - const enabled = stub(delivery, "enabled", () => true); + const send = vi + .spyOn(resend.emails, "send") + .mockImplementation(() => undefined as never); + const enabled = vi + .spyOn(delivery, "enabled") + .mockImplementation(() => true); stubs.push(send, enabled); const s = await seedTrial(); live = []; @@ -502,8 +528,12 @@ describe("trial lifecycle", () => { }); test("a trial the customer cancelled is audited as trial_cancelled", async () => { - const send = stub(resend.emails, "send"); - const enabled = stub(delivery, "enabled", () => true); + const send = vi + .spyOn(resend.emails, "send") + .mockImplementation(() => undefined as never); + const enabled = vi + .spyOn(delivery, "enabled") + .mockImplementation(() => true); stubs.push(send, enabled); const s = await seedTrial(); live = []; @@ -527,8 +557,12 @@ describe("trial lifecycle", () => { }); test("a converted trial cancelled later is churn even if the marker is stale", async () => { - const send = stub(resend.emails, "send"); - const enabled = stub(delivery, "enabled", () => true); + const send = vi + .spyOn(resend.emails, "send") + .mockImplementation(() => undefined as never); + const enabled = vi + .spyOn(delivery, "enabled") + .mockImplementation(() => true); stubs.push(send, enabled); // The trial-to-active webhook never landed, so `trialEndsAt` is set. const s = await seedTrial(); @@ -556,8 +590,12 @@ describe("trial lifecycle", () => { }); test("deleted on an already-free workspace → nothing happens", async () => { - const send = stub(resend.emails, "send"); - const enabled = stub(delivery, "enabled", () => true); + const send = vi + .spyOn(resend.emails, "send") + .mockImplementation(() => undefined as never); + const enabled = vi + .spyOn(delivery, "enabled") + .mockImplementation(() => true); stubs.push(send, enabled); const stripeId = `cus_${crypto.randomUUID()}`; await createTestWorkspace({ @@ -573,7 +611,7 @@ describe("trial lifecycle", () => { ), ); - assertSpyCalls(send, 0); + expect(send).toHaveBeenCalledTimes(0); }); }); }); diff --git a/packages/api/src/router/stripe/trial.test.ts b/packages/api/src/router/stripe/trial.test.ts index 2864d1296..6e438a485 100644 --- a/packages/api/src/router/stripe/trial.test.ts +++ b/packages/api/src/router/stripe/trial.test.ts @@ -2,10 +2,16 @@ import { db, eq } from "@openstatus/db"; import { invitation, workspace } from "@openstatus/db/src/schema"; import { getLimits } from "@openstatus/db/src/schema/plan/utils"; import { createTestWorkspace } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, type Stub, stub } from "@std/testing/mock"; import type Stripe from "stripe"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { stripe } from "./shared"; import { TRIAL_DAYS, maybeStartSignupTrial } from "./trial"; @@ -27,10 +33,10 @@ describe("maybeStartSignupTrial", () => { let customerId: string; let customers: Stripe.Customer[]; let price: Partial; - let stubs: Stub[]; - let list: Stub; - let createCustomer: Stub; - let createSubscription: Stub; + let stubs: MockInstance[]; + let list: MockInstance; + let createCustomer: MockInstance; + let createSubscription: MockInstance; // Runs inside `customers.create`, to interleave work with the Stripe call. let onCreateCustomer: (() => Promise) | undefined; @@ -39,46 +45,52 @@ describe("maybeStartSignupTrial", () => { customers = []; onCreateCustomer = undefined; price = { currency: "usd", currency_options: {} }; - list = stub( - stripe.customers, - "list", - () => - Promise.resolve({ data: customers } as Stripe.Response< - Stripe.ApiList - >) as Stripe.ApiListPromise, - ); - createCustomer = stub(stripe.customers, "create", async () => { - await onCreateCustomer?.(); - return { id: customerId } as Stripe.Response; - }); - createSubscription = stub(stripe.subscriptions, "create", () => - Promise.resolve({ - id: "sub_trial", - status: "trialing", - trial_end: trialEnd, - items: { - data: [ - { - price: { id: STARTER_PRICE }, - quantity: 1, - current_period_end: trialEnd, - }, - ], - }, - } as Stripe.Response), - ); + list = vi + .spyOn(stripe.customers, "list") + .mockImplementation( + () => + Promise.resolve({ data: customers } as Stripe.Response< + Stripe.ApiList + >) as Stripe.ApiListPromise, + ); + createCustomer = vi + .spyOn(stripe.customers, "create") + .mockImplementation(async () => { + await onCreateCustomer?.(); + return { id: customerId } as Stripe.Response; + }); + createSubscription = vi + .spyOn(stripe.subscriptions, "create") + .mockImplementation(() => + Promise.resolve({ + id: "sub_trial", + status: "trialing", + trial_end: trialEnd, + items: { + data: [ + { + price: { id: STARTER_PRICE }, + quantity: 1, + current_period_end: trialEnd, + }, + ], + }, + } as Stripe.Response), + ); stubs = [ list, createCustomer, createSubscription, - stub(stripe.prices, "retrieve", () => - Promise.resolve(price as Stripe.Response), - ), + vi + .spyOn(stripe.prices, "retrieve") + .mockImplementation(() => + Promise.resolve(price as Stripe.Response), + ), ]; }); afterEach(() => { - for (const s of stubs) s.restore(); + for (const s of stubs) s.mockRestore(); }); test("starts a Starter trial on the owner's workspace", async () => { @@ -100,8 +112,8 @@ describe("maybeStartSignupTrial", () => { const trialEndsAt = new Date(trialEnd * 1000); expect(result).toEqual({ started: true, trialEndsAt }); - assertSpyCalls(createCustomer, 1); - expect(createCustomer.calls[0]?.args).toEqual([ + expect(createCustomer).toHaveBeenCalledTimes(1); + expect(createCustomer.mock.calls[0]).toEqual([ { email: user.email, metadata: { workspaceId: String(ws.id), trialed: "true" }, @@ -109,8 +121,8 @@ describe("maybeStartSignupTrial", () => { { idempotencyKey: `trial-customer:ws_${ws.id}` }, ]); - assertSpyCalls(createSubscription, 1); - const [params, options] = createSubscription.calls[0]?.args ?? []; + expect(createSubscription).toHaveBeenCalledTimes(1); + const [params, options] = createSubscription.mock.calls[0] ?? []; expect(params).toMatchObject({ customer: customerId, currency: "eur", @@ -143,7 +155,7 @@ describe("maybeStartSignupTrial", () => { currency: "USD", }); - expect(createSubscription.calls[0]?.args[0]).toMatchObject({ + expect(createSubscription.mock.calls[0]?.[0]).toMatchObject({ currency: "eur", }); }); @@ -159,7 +171,7 @@ describe("maybeStartSignupTrial", () => { }); expect(result).toEqual({ started: false, reason: "sso" }); - assertSpyCalls(createCustomer, 0); + expect(createCustomer).toHaveBeenCalledTimes(0); }); test("skips magic-link sign-ins", async () => { @@ -173,7 +185,7 @@ describe("maybeStartSignupTrial", () => { }); expect(result).toEqual({ started: false, reason: "email" }); - assertSpyCalls(createCustomer, 0); + expect(createCustomer).toHaveBeenCalledTimes(0); }); test("skips users with a pending invitation", async () => { @@ -192,7 +204,7 @@ describe("maybeStartSignupTrial", () => { }); expect(result).toEqual({ started: false, reason: "invited" }); - assertSpyCalls(createCustomer, 0); + expect(createCustomer).toHaveBeenCalledTimes(0); }); test("skips disposable email domains", async () => { @@ -205,7 +217,7 @@ describe("maybeStartSignupTrial", () => { }); expect(result).toEqual({ started: false, reason: "disposable" }); - assertSpyCalls(list, 0); + expect(list).toHaveBeenCalledTimes(0); }); test("skips emails that already had a trial", async () => { @@ -222,11 +234,11 @@ describe("maybeStartSignupTrial", () => { }); expect(result).toEqual({ started: false, reason: "already_trialed" }); - expect(list.calls[0]?.args[0]).toEqual({ + expect(list.mock.calls[0]?.[0]).toEqual({ email: "o'brien@example.com", limit: 100, }); - assertSpyCalls(createCustomer, 0); + expect(createCustomer).toHaveBeenCalledTimes(0); }); test("ignores customers for the email that never trialed", async () => { @@ -240,7 +252,7 @@ describe("maybeStartSignupTrial", () => { }); expect(result).toMatchObject({ started: true }); - assertSpyCalls(createCustomer, 1); + expect(createCustomer).toHaveBeenCalledTimes(1); }); test("drops its customer when another request linked one first", async () => { @@ -253,9 +265,11 @@ describe("maybeStartSignupTrial", () => { .set({ stripeId: linked }) .where(eq(workspace.id, ws.id)); }; - const delCustomer = stub(stripe.customers, "del", () => - Promise.resolve({} as Stripe.Response), - ); + const delCustomer = vi + .spyOn(stripe.customers, "del") + .mockImplementation(() => + Promise.resolve({} as Stripe.Response), + ); stubs.push(delCustomer); const result = await maybeStartSignupTrial({ @@ -265,8 +279,8 @@ describe("maybeStartSignupTrial", () => { }); expect(result).toEqual({ started: false, reason: "not_eligible" }); - expect(delCustomer.calls[0]?.args[0]).toBe(customerId); - assertSpyCalls(createSubscription, 0); + expect(delCustomer.mock.calls[0]?.[0]).toBe(customerId); + expect(createSubscription).toHaveBeenCalledTimes(0); const after = await db .select({ stripeId: workspace.stripeId }) .from(workspace) @@ -285,6 +299,6 @@ describe("maybeStartSignupTrial", () => { }); expect(result).toEqual({ started: false, reason: "not_eligible" }); - assertSpyCalls(createCustomer, 0); + expect(createCustomer).toHaveBeenCalledTimes(0); }); }); diff --git a/packages/api/src/router/stripe/utils.test.ts b/packages/api/src/router/stripe/utils.test.ts index 491036e4e..e2037a3e6 100644 --- a/packages/api/src/router/stripe/utils.test.ts +++ b/packages/api/src/router/stripe/utils.test.ts @@ -2,9 +2,8 @@ import { getAddonPackSize, getLimits, } from "@openstatus/db/src/schema/plan/utils"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; import type Stripe from "stripe"; +import { describe, expect, test } from "vitest"; import { FEATURES, diff --git a/packages/api/src/router/stripe/webhook.test.ts b/packages/api/src/router/stripe/webhook.test.ts index b928588e6..fe484a54d 100644 --- a/packages/api/src/router/stripe/webhook.test.ts +++ b/packages/api/src/router/stripe/webhook.test.ts @@ -12,10 +12,16 @@ import { createUser, } from "@openstatus/db/src/test/factories"; import { delivery, resend } from "@openstatus/emails/src/send"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, type Stub, stub } from "@std/testing/mock"; import type Stripe from "stripe"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { createInnerTRPCContext } from "../../trpc"; import { stripe } from "./shared"; @@ -28,7 +34,7 @@ const DAY = 86_400; const now = () => Math.floor(Date.now() / 1000); // biome-ignore lint/suspicious/noExplicitAny: stubs over the Stripe and Resend clients -type AnyStub = Stub; +type AnyStub = MockInstance; // `current_period_end` lives on the items since API version 2025-03-31. function subscription( @@ -84,26 +90,32 @@ describe("stripe webhook emails", () => { live = []; // biome-ignore lint/suspicious/noExplicitAny: Resend result double const ok = { data: { id: "email_reminder_1" }, error: null } as any; - send = stub(resend.emails, "send", () => Promise.resolve(ok)); - batch = stub(resend.batch, "send", () => Promise.resolve(ok)); - cancelEmail = stub(resend.emails, "cancel", () => Promise.resolve(ok)); - updateSubscription = stub(stripe.subscriptions, "update", () => - // biome-ignore lint/suspicious/noExplicitAny: Stripe response double - Promise.resolve({ cancel_at_period_end: true } as any), - ); + send = vi + .spyOn(resend.emails, "send") + .mockImplementation(() => Promise.resolve(ok)); + batch = vi + .spyOn(resend.batch, "send") + .mockImplementation(() => Promise.resolve(ok)); + cancelEmail = vi + .spyOn(resend.emails, "cancel") + .mockImplementation(() => Promise.resolve(ok)); + updateSubscription = vi + .spyOn(stripe.subscriptions, "update") + .mockImplementation(() => + // biome-ignore lint/suspicious/noExplicitAny: Stripe response double + Promise.resolve({ cancel_at_period_end: true } as any), + ); stubs = [ send, batch, cancelEmail, updateSubscription, - stub(delivery, "enabled", () => true), - stub( - stripe.subscriptions, - "list", + vi.spyOn(delivery, "enabled").mockImplementation(() => true), + vi.spyOn(stripe.subscriptions, "list").mockImplementation( // biome-ignore lint/suspicious/noExplicitAny: auto-paging list double () => ({ autoPagingToArray: () => Promise.resolve(live) }) as any, ), - stub(stripe.customers, "retrieve", () => + vi.spyOn(stripe.customers, "retrieve").mockImplementation(() => Promise.resolve({ deleted: false, email: "Billing-Contact@example.test", @@ -114,19 +126,19 @@ describe("stripe webhook emails", () => { }); afterEach(() => { - for (const s of stubs) s.restore(); + for (const s of stubs) s.mockRestore(); }); function failSends() { - send.restore(); - batch.restore(); + send.mockRestore(); + batch.mockRestore(); stubs = stubs.filter((s) => s !== send && s !== batch); - send = stub(resend.emails, "send", () => - Promise.reject(new Error("resend is down")), - ); - batch = stub(resend.batch, "send", () => - Promise.reject(new Error("resend is down")), - ); + send = vi + .spyOn(resend.emails, "send") + .mockImplementation(() => Promise.reject(new Error("resend is down"))); + batch = vi + .spyOn(resend.batch, "send") + .mockImplementation(() => Promise.reject(new Error("resend is down"))); stubs.push(send, batch); } @@ -159,8 +171,8 @@ describe("stripe webhook emails", () => { .where(eq(workspace.id, s.workspace.id)) .get(); expect(ws?.plan).toBe("team"); - assertSpyCalls(send, 0); - assertSpyCalls(batch, 0); + expect(send).toHaveBeenCalledTimes(0); + expect(batch).toHaveBeenCalledTimes(0); }); test("none left → plan-downgraded to owners + customer, member-removed per trimmed member", async () => { @@ -190,8 +202,8 @@ describe("stripe webhook emails", () => { ); await caller().customerSubscriptionDeleted(evt); - assertSpyCalls(send, 1); - const [payload, options] = send.calls[0].args; + expect(send).toHaveBeenCalledTimes(1); + const [payload, options] = send.mock.calls[0]; expect(payload.to).toEqual([ s.ownerEmail, "Billing-Contact@example.test", @@ -204,8 +216,8 @@ describe("stripe webhook emails", () => { }); expect(payload.react.props.loss.membersRemoved).toBe(2); - assertSpyCalls(batch, 1); - const [emails, batchOptions] = batch.calls[0].args; + expect(batch).toHaveBeenCalledTimes(1); + const [emails, batchOptions] = batch.mock.calls[0]; expect(emails.map((e: { to: string }) => e.to)).toEqual([memberEmail]); expect(emails[0].html).toContain(s.ownerEmail); expect(batchOptions).toEqual({ @@ -243,8 +255,8 @@ describe("stripe webhook emails", () => { .where(eq(workspace.id, s.workspace.id)) .get(); expect(ws?.plan).toBe("free"); - assertSpyCalls(send, 1); - assertSpyCalls(batch, 1); + expect(send).toHaveBeenCalledTimes(1); + expect(batch).toHaveBeenCalledTimes(1); const members = await db .select() .from(usersToWorkspaces) @@ -270,8 +282,8 @@ describe("stripe webhook emails", () => { }); await caller().customerSubscriptionUpdated(evt); - assertSpyCalls(send, 2); - const [confirmation, confirmationOptions] = send.calls[0].args; + expect(send).toHaveBeenCalledTimes(2); + const [confirmation, confirmationOptions] = send.mock.calls[0]; expect(confirmation.subject).toBe("Your openstatus cancellation"); expect(confirmation.to).toEqual([ s.ownerEmail, @@ -281,7 +293,7 @@ describe("stripe webhook emails", () => { idempotencyKey: `stripe:${evt.event.id}:cancellation-scheduled`, }); - const [reminder, reminderOptions] = send.calls[1].args; + const [reminder, reminderOptions] = send.mock.calls[1]; expect(new Date(reminder.scheduledAt).getTime()).toBe( (sub.items.data[0].current_period_end - 3 * DAY) * 1000, ); @@ -289,8 +301,8 @@ describe("stripe webhook emails", () => { idempotencyKey: `stripe:${evt.event.id}:plan-ending-soon`, }); - assertSpyCalls(updateSubscription, 1); - expect(updateSubscription.calls[0].args).toEqual([ + expect(updateSubscription).toHaveBeenCalledTimes(1); + expect(updateSubscription.mock.calls[0]).toEqual([ sub.id, { metadata: { reminder_email_id: "email_reminder_1" } }, ]); @@ -310,9 +322,9 @@ describe("stripe webhook emails", () => { }), ); - assertSpyCalls(send, 1); - expect(send.calls[0].args[0].scheduledAt).toBeUndefined(); - assertSpyCalls(updateSubscription, 0); + expect(send).toHaveBeenCalledTimes(1); + expect(send.mock.calls[0][0].scheduledAt).toBeUndefined(); + expect(updateSubscription).toHaveBeenCalledTimes(0); }); test("true → false: the scheduled reminder is cancelled and the id cleared", async () => { @@ -328,10 +340,10 @@ describe("stripe webhook emails", () => { }), ); - assertSpyCalls(send, 0); - assertSpyCalls(cancelEmail, 1); - expect(cancelEmail.calls[0].args[0]).toBe("email_reminder_1"); - expect(updateSubscription.calls[0].args).toEqual([ + expect(send).toHaveBeenCalledTimes(0); + expect(cancelEmail).toHaveBeenCalledTimes(1); + expect(cancelEmail.mock.calls[0][0]).toBe("email_reminder_1"); + expect(updateSubscription.mock.calls[0]).toEqual([ sub.id, { metadata: { reminder_email_id: "" } }, ]); @@ -349,20 +361,22 @@ describe("stripe webhook emails", () => { ), ); - assertSpyCalls(send, 0); - assertSpyCalls(updateSubscription, 0); + expect(send).toHaveBeenCalledTimes(0); + expect(updateSubscription).toHaveBeenCalledTimes(0); }); test("a resume racing the metadata write cancels the fresh reminder", async () => { const s = await seed(); const sub = subscription(s.stripeId, { cancel_at_period_end: true }); live = [sub]; - updateSubscription.restore(); + updateSubscription.mockRestore(); stubs = stubs.filter((x) => x !== updateSubscription); - updateSubscription = stub(stripe.subscriptions, "update", () => - // biome-ignore lint/suspicious/noExplicitAny: Stripe response double - Promise.resolve({ cancel_at_period_end: false } as any), - ); + updateSubscription = vi + .spyOn(stripe.subscriptions, "update") + .mockImplementation(() => + // biome-ignore lint/suspicious/noExplicitAny: Stripe response double + Promise.resolve({ cancel_at_period_end: false } as any), + ); stubs.push(updateSubscription); await caller().customerSubscriptionUpdated( @@ -371,8 +385,8 @@ describe("stripe webhook emails", () => { }), ); - expect(cancelEmail.calls[0].args[0]).toBe("email_reminder_1"); - expect(updateSubscription.calls.map((c) => c.args[1])).toEqual([ + expect(cancelEmail.mock.calls[0][0]).toBe("email_reminder_1"); + expect(updateSubscription.mock.calls.map((c) => c[1])).toEqual([ { metadata: { reminder_email_id: "email_reminder_1" } }, { metadata: { reminder_email_id: "" } }, ]); @@ -384,9 +398,9 @@ describe("stripe webhook emails", () => { metadata: { reminder_email_id: "email_reminder_1" }, }); live = [sub]; - cancelEmail.restore(); + cancelEmail.mockRestore(); stubs = stubs.filter((x) => x !== cancelEmail); - cancelEmail = stub(resend.emails, "cancel", () => + cancelEmail = vi.spyOn(resend.emails, "cancel").mockImplementation(() => Promise.resolve({ data: null, error: { name: "application_error" }, @@ -401,18 +415,18 @@ describe("stripe webhook emails", () => { }), ); - assertSpyCalls(cancelEmail, 1); - assertSpyCalls(updateSubscription, 0); + expect(cancelEmail).toHaveBeenCalledTimes(1); + expect(updateSubscription).toHaveBeenCalledTimes(0); }); test("a failed confirmation does not suppress the reminder", async () => { const s = await seed(); const sub = subscription(s.stripeId, { cancel_at_period_end: true }); live = [sub]; - send.restore(); + send.mockRestore(); stubs = stubs.filter((x) => x !== send); let calls = 0; - send = stub(resend.emails, "send", () => + send = vi.spyOn(resend.emails, "send").mockImplementation(() => calls++ === 0 ? Promise.reject(new Error("resend is down")) : // biome-ignore lint/suspicious/noExplicitAny: Resend result double @@ -429,9 +443,9 @@ describe("stripe webhook emails", () => { }), ); - assertSpyCalls(send, 2); - expect(send.calls[1].args[0].scheduledAt).toBeDefined(); - assertSpyCalls(updateSubscription, 1); + expect(send).toHaveBeenCalledTimes(2); + expect(send.mock.calls[1][0].scheduledAt).toBeDefined(); + expect(updateSubscription).toHaveBeenCalledTimes(1); }); test("metadata-only update and unrelated updates send nothing", async () => { @@ -446,9 +460,9 @@ describe("stripe webhook emails", () => { event("customer.subscription.updated", sub), ); - assertSpyCalls(send, 0); - assertSpyCalls(cancelEmail, 0); - assertSpyCalls(updateSubscription, 0); + expect(send).toHaveBeenCalledTimes(0); + expect(cancelEmail).toHaveBeenCalledTimes(0); + expect(updateSubscription).toHaveBeenCalledTimes(0); }); test("a replayed event reuses the same idempotency keys", async () => { @@ -462,7 +476,7 @@ describe("stripe webhook emails", () => { await caller().customerSubscriptionUpdated(evt); await caller().customerSubscriptionUpdated(evt); - const keys = send.calls.map((c) => c.args[1].idempotencyKey); + const keys = send.mock.calls.map((c) => c[1].idempotencyKey); expect(keys.slice(0, 2)).toEqual(keys.slice(2, 4)); }); @@ -547,8 +561,8 @@ describe("stripe webhook emails", () => { await caller().customerSubscriptionTrialWillEnd(evt); - assertSpyCalls(send, 1); - const [payload, options] = send.calls[0].args; + expect(send).toHaveBeenCalledTimes(1); + const [payload, options] = send.mock.calls[0]; expect(payload.subject).toContain("add a payment method to keep"); expect(payload.to).toContain(s.ownerEmail); expect(payload.react.props).toMatchObject({ @@ -584,8 +598,8 @@ describe("stripe webhook emails", () => { ), ); - assertSpyCalls(send, 1); - const [payload] = send.calls[0].args; + expect(send).toHaveBeenCalledTimes(1); + const [payload] = send.mock.calls[0]; expect(payload.subject).toContain("plan continues"); expect(payload.react.props).toMatchObject({ hasPaymentMethod: true }); expect(payload.react.props.loss).toBeUndefined(); @@ -598,7 +612,7 @@ describe("stripe webhook emails", () => { subscription(s.stripeId, { status: "active", trial_end: now() - 1 }), ); await caller().customerSubscriptionTrialWillEnd(evt); - assertSpyCalls(send, 0); + expect(send).toHaveBeenCalledTimes(0); }); test("a mail failure fails the webhook so Stripe redelivers", async () => { @@ -616,9 +630,9 @@ describe("stripe webhook emails", () => { test("a Resend error response fails the webhook too", async () => { const s = await seed(); - send.restore(); + send.mockRestore(); stubs = stubs.filter((x) => x !== send); - send = stub(resend.emails, "send", () => + send = vi.spyOn(resend.emails, "send").mockImplementation(() => Promise.resolve({ data: null, error: { name: "application_error", message: "boom" }, @@ -648,7 +662,7 @@ describe("stripe webhook emails", () => { subscription(s.stripeId, { trial_end: null }), ), ); - assertSpyCalls(send, 0); + expect(send).toHaveBeenCalledTimes(0); }); }); }); diff --git a/packages/api/src/router/tinybird/index.test.ts b/packages/api/src/router/tinybird/index.test.ts index bd16f0471..369b50f3a 100644 --- a/packages/api/src/router/tinybird/index.test.ts +++ b/packages/api/src/router/tinybird/index.test.ts @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; import { TRPCError } from "@trpc/server"; +import { expect, test } from "vitest"; import { edgeRouter } from "../../edge"; import { createInnerTRPCContext } from "../../trpc"; diff --git a/packages/api/src/router/workspace.test.ts b/packages/api/src/router/workspace.test.ts index 3812aff70..4b62c5ffa 100644 --- a/packages/api/src/router/workspace.test.ts +++ b/packages/api/src/router/workspace.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; +import { expect, test } from "vitest"; import { edgeRouter } from "../edge"; import { createInnerTRPCContext } from "../trpc"; diff --git a/packages/api/src/test/upstash.mock.ts b/packages/api/src/test/upstash.mock.ts index df729463e..439c2d618 100644 --- a/packages/api/src/test/upstash.mock.ts +++ b/packages/api/src/test/upstash.mock.ts @@ -1,5 +1,5 @@ -// Test double for @openstatus/upstash, swapped in via --import-map in the -// `deno test` command. Keeps tests off a real Upstash Redis instance. +// Test double for @openstatus/upstash, swapped in via test.importmap.json. +// Keeps tests off a real Upstash Redis instance. const noopRedis = { get: () => Promise.resolve(null), set: () => Promise.resolve("OK"), diff --git a/packages/api/src/trpc-errors.test.ts b/packages/api/src/trpc-errors.test.ts index c367350f7..48f4be8c6 100644 --- a/packages/api/src/trpc-errors.test.ts +++ b/packages/api/src/trpc-errors.test.ts @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; import { TRPCError } from "@trpc/server"; +import { describe, expect, test } from "vitest"; import { EXPECTED_TRPC_CODES, diff --git a/packages/api/src/trpc.test.ts b/packages/api/src/trpc.test.ts index 82887bec2..bd5460068 100644 --- a/packages/api/src/trpc.test.ts +++ b/packages/api/src/trpc.test.ts @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { test } from "@std/testing/bdd"; import { NextRequest } from "next/server.js"; +import { expect, test } from "vitest"; import type { ResolveActiveWorkspaceResult } from "./auth/resolve-active-workspace"; import { diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 314dbdf90..1d8cbf353 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -5,14 +5,5 @@ "declaration": false, "declarationMap": false, "resolveJsonModule": true - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/api/vitest.config.ts b/packages/api/vitest.config.ts new file mode 100644 index 000000000..8b57687a1 --- /dev/null +++ b/packages/api/vitest.config.ts @@ -0,0 +1,6 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname, { + importMap: "test.importmap.json", + envFile: ".env.test", +}); diff --git a/packages/assertions/package.json b/packages/assertions/package.json index e28403cb2..f9eb2b602 100644 --- a/packages/assertions/package.json +++ b/packages/assertions/package.json @@ -8,16 +8,16 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports" + "test": "vitest run" }, "dependencies": { "jsonpath-plus": "catalog:", "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/assertions/src/serializing.test.ts b/packages/assertions/src/serializing.test.ts index 5ae7c405f..7aaa8f1ea 100644 --- a/packages/assertions/src/serializing.test.ts +++ b/packages/assertions/src/serializing.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { deserialize, serialize } from "./serializing"; import { diff --git a/packages/assertions/src/v1.test.ts b/packages/assertions/src/v1.test.ts index f9f060c17..795e84875 100644 --- a/packages/assertions/src/v1.test.ts +++ b/packages/assertions/src/v1.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import type { HttpAssertionRequest } from "./types"; import { @@ -198,19 +197,19 @@ describe("JsonBodyAssertion", () => { // JSONPath returns an array, which is compared to the target as-is, so // these fail today. Un-ignore once the matched value is unwrapped. - it.ignore("passes eq when the matched value equals the target", () => { + it.skip("passes eq when the matched value equals the target", () => { expect(json("$.status", "eq", "ok").assert(http({ body })).success).toBe( true, ); }); - it.ignore("fails not_eq when the matched value equals the target", () => { + it.skip("fails not_eq when the matched value equals the target", () => { expect( json("$.status", "not_eq", "ok").assert(http({ body })).success, ).toBe(false); }); - it.ignore("matches substrings with contains", () => { + it.skip("matches substrings with contains", () => { expect( json("$.status", "contains", "o").assert(http({ body })).success, ).toBe(true); diff --git a/packages/assertions/tsconfig.json b/packages/assertions/tsconfig.json index 9f7ee457b..aed15c74a 100644 --- a/packages/assertions/tsconfig.json +++ b/packages/assertions/tsconfig.json @@ -1,13 +1,4 @@ { "extends": "@openstatus/tsconfig/base.json", - "include": ["src", "*.ts"], - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + "include": ["src", "*.ts"] } diff --git a/packages/assertions/vitest.config.ts b/packages/assertions/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/assertions/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/db/package.json b/packages/db/package.json index 0b2717e57..a231fea61 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -45,12 +45,11 @@ }, "devDependencies": { "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/bcryptjs": "catalog:", "@types/node": "catalog:", "drizzle-kit": "catalog:", "next-auth": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/db/script/region-migration.test.ts b/packages/db/script/region-migration.test.ts index 38c6bc261..7df3227d1 100644 --- a/packages/db/script/region-migration.test.ts +++ b/packages/db/script/region-migration.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { beforeEach, describe, test } from "@std/testing/bdd"; +import { beforeEach, describe, expect, test } from "vitest"; import type { z } from "zod"; import type { monitorRegionSchema } from "../src/schema/constants"; diff --git a/packages/db/src/schema/plan/utils.test.ts b/packages/db/src/schema/plan/utils.test.ts index 620af10cb..1d931bb24 100644 --- a/packages/db/src/schema/plan/utils.test.ts +++ b/packages/db/src/schema/plan/utils.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { allPlans } from "./config"; import { YEARLY_ADDON_MULTIPLIER, getAddonPriceConfig } from "./utils"; diff --git a/packages/db/src/utils/api-key.test.ts b/packages/db/src/utils/api-key.test.ts index 4d987dda4..3dd625755 100644 --- a/packages/db/src/utils/api-key.test.ts +++ b/packages/db/src/utils/api-key.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { generateApiKey, diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json index 0c9678a59..f7a685380 100644 --- a/packages/db/tsconfig.json +++ b/packages/db/tsconfig.json @@ -1,15 +1,7 @@ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@openstatus/tsconfig/base.json", - "exclude": [ - "dist", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ], + "exclude": ["dist"], "compilerOptions": { "outDir": "dist", "target": "es2021", diff --git a/packages/emails/package.json b/packages/emails/package.json index 14791886e..a195c29ab 100644 --- a/packages/emails/package.json +++ b/packages/emails/package.json @@ -14,7 +14,7 @@ "scripts": { "check": "tsc --noEmit", "dev:email": "email dev", - "test": "deno test --parallel -A --no-check --sloppy-imports" + "test": "vitest run" }, "dependencies": { "@openstatus/utils": "workspace:*", @@ -27,12 +27,12 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", "@react-email/ui": "catalog:", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", "@types/react": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/emails/src/billing.test.tsx b/packages/emails/src/billing.test.tsx index 7586ea2e1..c1a6f4480 100644 --- a/packages/emails/src/billing.test.tsx +++ b/packages/emails/src/billing.test.tsx @@ -1,10 +1,16 @@ /** @jsxRuntime automatic @jsxImportSource react */ import "./test-preload.ts"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, type Stub, stub } from "@std/testing/mock"; import { render } from "react-email"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import type { PlanLoss } from "../emails/_components/plan-loss"; import CancellationScheduledEmail from "../emails/cancellation-scheduled"; @@ -288,20 +294,25 @@ describe("reminderScheduledAt", () => { }); describe("send plumbing", () => { - let send: Stub, batch: Stub, cancel: Stub, enabled: Stub; + let send: MockInstance, + batch: MockInstance, + cancel: MockInstance, + enabled: MockInstance; // safe because the verbs only read `data.id` and `error.name` const result = (value: unknown) => Promise.resolve(value as never); const ok = { data: { id: "email_123" }, error: null }; beforeEach(() => { - enabled = stub(delivery, "enabled", () => true); - send = stub(resend.emails, "send", () => result(ok)); - batch = stub(resend.batch, "send", () => result(ok)); - cancel = stub(resend.emails, "cancel", () => result(ok)); + enabled = vi.spyOn(delivery, "enabled").mockImplementation(() => true); + send = vi.spyOn(resend.emails, "send").mockImplementation(() => result(ok)); + batch = vi.spyOn(resend.batch, "send").mockImplementation(() => result(ok)); + cancel = vi + .spyOn(resend.emails, "cancel") + .mockImplementation(() => result(ok)); }); afterEach(() => { - for (const s of [enabled, send, batch, cancel]) s.restore(); + for (const s of [enabled, send, batch, cancel]) s.mockRestore(); }); const email = { @@ -324,43 +335,45 @@ describe("send plumbing", () => { { idempotencyKey: "k", scheduledAt: "2026-09-22T00:00:00.000Z" }, ); expect(id).toBe("email_123"); - expect(send.calls[0].args[0].scheduledAt).toBe("2026-09-22T00:00:00.000Z"); - expect(send.calls[0].args[0].replyTo).toBe("r@openstatus.dev"); - expect(send.calls[0].args[1]).toEqual({ idempotencyKey: "k" }); + expect(send.mock.calls[0][0].scheduledAt).toBe("2026-09-22T00:00:00.000Z"); + expect(send.mock.calls[0][0].replyTo).toBe("r@openstatus.dev"); + expect(send.mock.calls[0][1]).toEqual({ idempotencyKey: "k" }); }); test("sendEmail omits both when not given", async () => { await sendEmail(email); - expect("scheduledAt" in send.calls[0].args[0]).toBe(false); - expect(send.calls[0].args[1]).toBeUndefined(); + expect("scheduledAt" in send.mock.calls[0][0]).toBe(false); + expect(send.mock.calls[0][1]).toBeUndefined(); }); test("sendEmail does nothing outside production", async () => { - enabled.restore(); - enabled = stub(delivery, "enabled", () => false); + enabled.mockRestore(); + enabled = vi.spyOn(delivery, "enabled").mockImplementation(() => false); expect(await sendEmail(email)).toBeUndefined(); - assertSpyCalls(send, 0); + expect(send).toHaveBeenCalledTimes(0); }); test("an idempotency conflict or Resend error never throws", async () => { for (const name of ["invalid_idempotent_request", "application_error"]) { - send.restore(); - send = stub(resend.emails, "send", () => - result({ data: null, error: { name } }), - ); + send.mockRestore(); + send = vi + .spyOn(resend.emails, "send") + .mockImplementation(() => result({ data: null, error: { name } })); expect(await sendEmail(email, { idempotencyKey: "k" })).toBeUndefined(); - assertSpyCalls(send, 1); + expect(send).toHaveBeenCalledTimes(1); } }); test("cancelScheduledEmail wraps emails.cancel and reports success", async () => { expect(await cancelScheduledEmail("email_123")).toBe(true); - expect(cancel.calls[0].args[0]).toBe("email_123"); + expect(cancel.mock.calls[0][0]).toBe("email_123"); - cancel.restore(); - cancel = stub(resend.emails, "cancel", () => - result({ data: null, error: { name: "application_error" } }), - ); + cancel.mockRestore(); + cancel = vi + .spyOn(resend.emails, "cancel") + .mockImplementation(() => + result({ data: null, error: { name: "application_error" } }), + ); expect(await cancelScheduledEmail("email_123")).toBe(false); }); @@ -372,9 +385,9 @@ describe("send plumbing", () => { reason: "downgrade", owners: [], }); - assertSpyCalls(batch, 3); - expect(batch.calls.map((c) => c.args[0].length)).toEqual([100, 100, 50]); - expect(batch.calls.map((c) => c.args[1].idempotencyKey)).toEqual([ + expect(batch).toHaveBeenCalledTimes(3); + expect(batch.mock.calls.map((c) => c[0].length)).toEqual([100, 100, 50]); + expect(batch.mock.calls.map((c) => c[1].idempotencyKey)).toEqual([ "stripe:evt_1:member-removed:0", "stripe:evt_1:member-removed:1", "stripe:evt_1:member-removed:2", @@ -382,17 +395,19 @@ describe("send plumbing", () => { }); test("a batch error is swallowed, not thrown", async () => { - batch.restore(); - batch = stub(resend.batch, "send", () => - result({ data: null, error: { name: "application_error" } }), - ); + batch.mockRestore(); + batch = vi + .spyOn(resend.batch, "send") + .mockImplementation(() => + result({ data: null, error: { name: "application_error" } }), + ); await sendMemberRemoved({ to: ["a@acme.dev"], workspaceName: "Acme", reason: "downgrade", owners: [], }); - assertSpyCalls(batch, 1); + expect(batch).toHaveBeenCalledTimes(1); }); test("sendPlanDowngraded", async () => { @@ -403,7 +418,7 @@ describe("send plumbing", () => { previousPlan: "team", loss, }); - const [payload, options] = send.calls[0].args; + const [payload, options] = send.mock.calls[0]; expect(payload.to).toEqual(["max@acme.dev"]); expect(payload.from).toBe( "Thibault from openstatus ", @@ -423,7 +438,7 @@ describe("send plumbing", () => { reason: "downgrade", owners: ["max@acme.dev"], }); - const [emails, options] = batch.calls[0].args; + const [emails, options] = batch.mock.calls[0]; expect(emails.map((e: { to: string }) => e.to)).toEqual([ "a@acme.dev", "b@acme.dev", @@ -449,7 +464,7 @@ describe("send plumbing", () => { endsAt, loss, }); - const [payload, options] = send.calls[0].args; + const [payload, options] = send.mock.calls[0]; expect(payload.from).toBe( "Thibault from openstatus ", ); @@ -470,7 +485,7 @@ describe("send plumbing", () => { now: new Date("2026-09-01T00:00:00Z"), }); expect(id).toBe("email_123"); - const [payload, options] = send.calls[0].args; + const [payload, options] = send.mock.calls[0]; expect(payload.from).toBe( "openstatus ", ); @@ -492,7 +507,7 @@ describe("send plumbing", () => { now: new Date("2026-09-01T00:00:00Z"), }); expect(id).toBeUndefined(); - assertSpyCalls(send, 0); + expect(send).toHaveBeenCalledTimes(0); }); test("sendTrialEnding", async () => { @@ -505,7 +520,7 @@ describe("send plumbing", () => { hasPaymentMethod: false, loss, }); - const [payload, options] = send.calls[0].args; + const [payload, options] = send.mock.calls[0]; expect(payload.from).toBe( "openstatus ", ); @@ -538,7 +553,7 @@ describe("send plumbing", () => { plan: "starter", hasPaymentMethod: false, }); - assertSpyCalls(send, 0); - assertSpyCalls(batch, 0); + expect(send).toHaveBeenCalledTimes(0); + expect(batch).toHaveBeenCalledTimes(0); }); }); diff --git a/packages/emails/src/client.integration.test.tsx b/packages/emails/src/client.integration.test.tsx index f594f6e47..94f5487d5 100644 --- a/packages/emails/src/client.integration.test.tsx +++ b/packages/emails/src/client.integration.test.tsx @@ -1,9 +1,8 @@ /** @jsxRuntime automatic @jsxImportSource react */ import "./test-preload.ts"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; import { render } from "react-email"; +import { describe, expect, test } from "vitest"; import PageUpdateEmail from "../emails/page-update"; import { pageUpdateSubject } from "./client"; diff --git a/packages/emails/src/client.test.ts b/packages/emails/src/client.test.ts index 6879f8a35..13e375f2e 100644 --- a/packages/emails/src/client.test.ts +++ b/packages/emails/src/client.test.ts @@ -1,12 +1,13 @@ import "./test-preload.ts"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; import { - assertSpyCalls, - returnsNext, - type Stub, - stub, -} from "@std/testing/mock"; + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { EmailClient } from "./client"; @@ -46,7 +47,7 @@ const idempotencyConflict = { describe("EmailClient.sendPageUpdate - idempotency & chunking", () => { let client: EmailClient; // biome-ignore lint/suspicious/noExplicitAny: stub over the Resend batch method - let batchSend: Stub; + let batchSend: MockInstance; beforeEach(() => { // zero backoff so the retry test doesn't wait on the real exponential sleep @@ -54,11 +55,13 @@ describe("EmailClient.sendPageUpdate - idempotency & chunking", () => { apiKey: "re_test_123", retryBackoff: "0 millis", }); - batchSend = stub(client.client.batch, "send", () => Promise.resolve(ok)); + batchSend = vi + .spyOn(client.client.batch, "send") + .mockImplementation(() => Promise.resolve(ok)); }); afterEach(() => { - batchSend.restore(); + batchSend.mockRestore(); }); test("passes the base idempotency key suffixed with the batch index", async () => { @@ -66,8 +69,8 @@ describe("EmailClient.sendPageUpdate - idempotency & chunking", () => { baseReq({ idempotencyKey: "status-report-update:5" }), ); - assertSpyCalls(batchSend, 1); - const options = batchSend.calls[0].args[1]; + expect(batchSend).toHaveBeenCalledTimes(1); + const options = batchSend.mock.calls[0][1]; expect(options).toEqual({ idempotencyKey: "status-report-update:5:0" }); }); @@ -79,31 +82,30 @@ describe("EmailClient.sendPageUpdate - idempotency & chunking", () => { }), ); - assertSpyCalls(batchSend, 3); - const keys = batchSend.calls.map((c) => c.args[1]?.idempotencyKey); + expect(batchSend).toHaveBeenCalledTimes(3); + const keys = batchSend.mock.calls.map((c) => c[1]?.idempotencyKey); expect(keys).toEqual([ "status-report-update:9:0", "status-report-update:9:1", "status-report-update:9:2", ]); - const sizes = batchSend.calls.map((c) => c.args[0].length); + const sizes = batchSend.mock.calls.map((c) => c[0].length); expect(sizes).toEqual([100, 100, 50]); }); test("omits the option entirely when no base key is provided", async () => { await client.sendPageUpdate(baseReq()); - const options = batchSend.calls[0].args[1]; + const options = batchSend.mock.calls[0][1]; expect(options).toBeUndefined(); }); test("reuses the same key across a retry so Resend dedupes the resend", async () => { - batchSend.restore(); - batchSend = stub( - client.client.batch, - "send", - returnsNext([Promise.resolve(fail), Promise.resolve(ok)]), - ); + batchSend.mockRestore(); + batchSend = vi + .spyOn(client.client.batch, "send") + .mockResolvedValueOnce(fail) + .mockResolvedValueOnce(ok); await client.sendPageUpdate( baseReq({ idempotencyKey: "status-report-update:7" }), @@ -111,8 +113,8 @@ describe("EmailClient.sendPageUpdate - idempotency & chunking", () => { // failure → retry: the second attempt must carry the identical key, or // Resend would treat the retry as a fresh batch and double-send. - assertSpyCalls(batchSend, 2); - const keys = batchSend.calls.map((c) => c.args[1]?.idempotencyKey); + expect(batchSend).toHaveBeenCalledTimes(2); + const keys = batchSend.mock.calls.map((c) => c[1]?.idempotencyKey); expect(keys).toEqual([ "status-report-update:7:0", "status-report-update:7:0", @@ -120,10 +122,10 @@ describe("EmailClient.sendPageUpdate - idempotency & chunking", () => { }); test("does not retry a deterministic idempotency conflict", async () => { - batchSend.restore(); - batchSend = stub(client.client.batch, "send", () => - Promise.resolve(idempotencyConflict), - ); + batchSend.mockRestore(); + batchSend = vi + .spyOn(client.client.batch, "send") + .mockImplementation(() => Promise.resolve(idempotencyConflict)); await client.sendPageUpdate( baseReq({ idempotencyKey: "status-report-update:7" }), @@ -131,22 +133,24 @@ describe("EmailClient.sendPageUpdate - idempotency & chunking", () => { // a 409 invalid_idempotent_request replays identically on every attempt; // retrying only re-logs the same error. - assertSpyCalls(batchSend, 1); + expect(batchSend).toHaveBeenCalledTimes(1); }); }); describe("EmailClient.sendMonitorAlert", () => { let client: EmailClient; // biome-ignore lint/suspicious/noExplicitAny: stub over the Resend send method - let send: Stub; + let send: MockInstance; beforeEach(() => { client = new EmailClient({ apiKey: "re_test_123" }); - send = stub(client.client.emails, "send", () => Promise.resolve(ok)); + send = vi + .spyOn(client.client.emails, "send") + .mockImplementation(() => Promise.resolve(ok)); }); afterEach(() => { - send.restore(); + send.mockRestore(); }); const req = { @@ -164,8 +168,8 @@ describe("EmailClient.sendMonitorAlert", () => { test("sends the rendered template from the system sender", async () => { await client.sendMonitorAlert(req); - assertSpyCalls(send, 1); - const payload = send.calls[0].args[0]; + expect(send).toHaveBeenCalledTimes(1); + const payload = send.mock.calls[0][0]; expect(payload.to).toBe("ping@openstatus.dev"); expect(payload.from).toBe( "openstatus ", @@ -180,8 +184,10 @@ describe("EmailClient.sendMonitorAlert", () => { }); test("rethrows a Resend error so the outbox can retry", async () => { - send.restore(); - send = stub(client.client.emails, "send", () => Promise.resolve(fail)); + send.mockRestore(); + send = vi + .spyOn(client.client.emails, "send") + .mockImplementation(() => Promise.resolve(fail)); await expect(client.sendMonitorAlert(req)).rejects.toEqual({ name: "application_error", @@ -192,15 +198,17 @@ describe("EmailClient.sendMonitorAlert", () => { describe("EmailClient.sendPrivateLocationAlert", () => { let client: EmailClient; // biome-ignore lint/suspicious/noExplicitAny: stub over the Resend batch method - let batchSend: Stub; + let batchSend: MockInstance; beforeEach(() => { client = new EmailClient({ apiKey: "re_test_123" }); - batchSend = stub(client.client.batch, "send", () => Promise.resolve(ok)); + batchSend = vi + .spyOn(client.client.batch, "send") + .mockImplementation(() => Promise.resolve(ok)); }); afterEach(() => { - batchSend.restore(); + batchSend.mockRestore(); }); const req = { @@ -214,8 +222,8 @@ describe("EmailClient.sendPrivateLocationAlert", () => { test("sends one mail per member with subject, sender and body", async () => { await client.sendPrivateLocationAlert(req); - assertSpyCalls(batchSend, 1); - const emails = batchSend.calls[0].args[0]; + expect(batchSend).toHaveBeenCalledTimes(1); + const emails = batchSend.mock.calls[0][0]; expect(emails.map((e: { to: string }) => e.to)).toEqual(req.to); expect(emails[0].from).toBe( "openstatus ", @@ -229,13 +237,15 @@ describe("EmailClient.sendPrivateLocationAlert", () => { test("does nothing without recipients", async () => { await client.sendPrivateLocationAlert({ ...req, to: [] }); - assertSpyCalls(batchSend, 0); + expect(batchSend).toHaveBeenCalledTimes(0); }); test("swallows a non rate-limit failure", async () => { - batchSend.restore(); - batchSend = stub(client.client.batch, "send", () => Promise.resolve(fail)); + batchSend.mockRestore(); + batchSend = vi + .spyOn(client.client.batch, "send") + .mockImplementation(() => Promise.resolve(fail)); await client.sendPrivateLocationAlert(req); - assertSpyCalls(batchSend, 1); + expect(batchSend).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/emails/src/format.test.ts b/packages/emails/src/format.test.ts index 678822f0a..f1627b075 100644 --- a/packages/emails/src/format.test.ts +++ b/packages/emails/src/format.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { formatDateTime, diff --git a/packages/emails/src/templates.test.tsx b/packages/emails/src/templates.test.tsx index 41d02e2e7..dc9fe3aa4 100644 --- a/packages/emails/src/templates.test.tsx +++ b/packages/emails/src/templates.test.tsx @@ -1,9 +1,8 @@ /** @jsxRuntime automatic @jsxImportSource react */ import "./test-preload.ts"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; import { render } from "react-email"; +import { describe, expect, test } from "vitest"; import { Callout } from "../emails/_components/callout"; import { Footer, POSTAL_ADDRESS } from "../emails/_components/footer"; diff --git a/packages/emails/src/test-preload.ts b/packages/emails/src/test-preload.ts index 16a592413..4f3780caf 100644 --- a/packages/emails/src/test-preload.ts +++ b/packages/emails/src/test-preload.ts @@ -2,8 +2,7 @@ * Test env setup. Import this FIRST in any test file so env vars are set * before module-level env validations fire on import. * - * Bun's test runner set NODE_ENV=test automatically; Deno does not, so we set - * it here. `./env` validates RESEND_API_KEY at module load and snapshots + * `./env` validates RESEND_API_KEY at module load and snapshots * NODE_ENV, so both have to be in place before `./client` or `./send` load. */ process.env.NODE_ENV = "test"; diff --git a/packages/emails/tsconfig.json b/packages/emails/tsconfig.json index 3d4b6a76f..39fe813f1 100644 --- a/packages/emails/tsconfig.json +++ b/packages/emails/tsconfig.json @@ -1,17 +1,7 @@ { "extends": "@openstatus/tsconfig/base.json", "include": [".", "src/env.ts"], - "exclude": [ - "dist", - "build", - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ], + "exclude": ["dist", "build", "node_modules"], "compilerOptions": { "jsx": "react-jsx", "resolveJsonModule": true diff --git a/packages/emails/vitest.config.ts b/packages/emails/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/emails/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/error/tsconfig.json b/packages/error/tsconfig.json index 9f7ee457b..aed15c74a 100644 --- a/packages/error/tsconfig.json +++ b/packages/error/tsconfig.json @@ -1,13 +1,4 @@ { "extends": "@openstatus/tsconfig/base.json", - "include": ["src", "*.ts"], - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + "include": ["src", "*.ts"] } diff --git a/packages/header-analysis/package.json b/packages/header-analysis/package.json index 105de0e4a..9c777efd1 100644 --- a/packages/header-analysis/package.json +++ b/packages/header-analysis/package.json @@ -8,14 +8,14 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", - "test:watch": "deno test --parallel -A --no-check --sloppy-imports --watch" + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": {}, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/header-analysis/src/cdn/detect-cdn.test.ts b/packages/header-analysis/src/cdn/detect-cdn.test.ts index 7d7def9e2..997669c1c 100644 --- a/packages/header-analysis/src/cdn/detect-cdn.test.ts +++ b/packages/header-analysis/src/cdn/detect-cdn.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { detectCdn } from "./detect-cdn"; diff --git a/packages/header-analysis/src/cdn/edge-pop.test.ts b/packages/header-analysis/src/cdn/edge-pop.test.ts index bbec53c1a..40e1c2d80 100644 --- a/packages/header-analysis/src/cdn/edge-pop.test.ts +++ b/packages/header-analysis/src/cdn/edge-pop.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { extractEdgePop } from "./edge-pop"; diff --git a/packages/header-analysis/src/cdn/infer-topology.test.ts b/packages/header-analysis/src/cdn/infer-topology.test.ts index 05eeb786a..fa730dfb9 100644 --- a/packages/header-analysis/src/cdn/infer-topology.test.ts +++ b/packages/header-analysis/src/cdn/infer-topology.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { inferTopology } from "./infer-topology"; diff --git a/packages/header-analysis/src/cdn/normalize-cache-status.test.ts b/packages/header-analysis/src/cdn/normalize-cache-status.test.ts index f6f3743c4..e00de1f15 100644 --- a/packages/header-analysis/src/cdn/normalize-cache-status.test.ts +++ b/packages/header-analysis/src/cdn/normalize-cache-status.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { normalizeCacheStatus } from "./normalize-cache-status"; diff --git a/packages/header-analysis/src/parser/cache-control.test.ts b/packages/header-analysis/src/parser/cache-control.test.ts index 487bd6ad2..cda81f377 100644 --- a/packages/header-analysis/src/parser/cache-control.test.ts +++ b/packages/header-analysis/src/parser/cache-control.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { parseCacheControlHeader } from "./cache-control"; diff --git a/packages/header-analysis/src/parser/cf-cache-status.test.ts b/packages/header-analysis/src/parser/cf-cache-status.test.ts index d5a81eb45..e46bcd5c8 100644 --- a/packages/header-analysis/src/parser/cf-cache-status.test.ts +++ b/packages/header-analysis/src/parser/cf-cache-status.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { parseCfCacheStatus } from "./cf-cache-status"; diff --git a/packages/header-analysis/src/parser/cf-ray.test.ts b/packages/header-analysis/src/parser/cf-ray.test.ts index 10d586649..33c980994 100644 --- a/packages/header-analysis/src/parser/cf-ray.test.ts +++ b/packages/header-analysis/src/parser/cf-ray.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { parseCfRay } from "./cf-ray"; diff --git a/packages/header-analysis/src/parser/fly-request-id.test.ts b/packages/header-analysis/src/parser/fly-request-id.test.ts index c3fd32bdd..cf6ee7547 100644 --- a/packages/header-analysis/src/parser/fly-request-id.test.ts +++ b/packages/header-analysis/src/parser/fly-request-id.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { parseFlyRequestId } from "./fly-request-id"; diff --git a/packages/header-analysis/src/parser/x-vercel-cache.test.ts b/packages/header-analysis/src/parser/x-vercel-cache.test.ts index 2780dd223..fc1c67c77 100644 --- a/packages/header-analysis/src/parser/x-vercel-cache.test.ts +++ b/packages/header-analysis/src/parser/x-vercel-cache.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { parseXVercelCache } from "./x-vercel-cache"; diff --git a/packages/header-analysis/src/parser/x-vercel-id.test.ts b/packages/header-analysis/src/parser/x-vercel-id.test.ts index fd1c0f31d..01560a603 100644 --- a/packages/header-analysis/src/parser/x-vercel-id.test.ts +++ b/packages/header-analysis/src/parser/x-vercel-id.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { parseXVercelId } from "./x-vercel-id"; diff --git a/packages/header-analysis/tsconfig.json b/packages/header-analysis/tsconfig.json index 76722cdc6..48baa5cfe 100644 --- a/packages/header-analysis/tsconfig.json +++ b/packages/header-analysis/tsconfig.json @@ -3,14 +3,5 @@ "include": ["src", "*.ts"], "compilerOptions": { "resolveJsonModule": true - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/header-analysis/vitest.config.ts b/packages/header-analysis/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/header-analysis/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/icons/tsconfig.json b/packages/icons/tsconfig.json index e1d27cbd2..cec44c664 100644 --- a/packages/icons/tsconfig.json +++ b/packages/icons/tsconfig.json @@ -1,17 +1,7 @@ { "extends": "@openstatus/tsconfig/react-library.json", "include": ["."], - "exclude": [ - "dist", - "build", - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ], + "exclude": ["dist", "build", "node_modules"], "compilerOptions": { "paths": { "@/*": ["./src/*"] diff --git a/packages/importers/package.json b/packages/importers/package.json index 742f8fbeb..c472a50ff 100644 --- a/packages/importers/package.json +++ b/packages/importers/package.json @@ -17,8 +17,8 @@ }, "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", - "test:watch": "deno test --parallel -A --no-check --sloppy-imports --watch", + "test": "vitest run", + "test:watch": "vitest", "tsc": "tsc --noEmit" }, "dependencies": { @@ -26,9 +26,9 @@ }, "devDependencies": { "@openstatus/regions": "workspace:*", + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/importers/src/providers/betterstack/client.test.ts b/packages/importers/src/providers/betterstack/client.test.ts index fbcd501e7..47c0792c9 100644 --- a/packages/importers/src/providers/betterstack/client.test.ts +++ b/packages/importers/src/providers/betterstack/client.test.ts @@ -1,6 +1,12 @@ -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, spy } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type Mock, +} from "vitest"; import { createBetterstackClient } from "./client"; import { @@ -18,7 +24,7 @@ const originalFetch = globalThis.fetch; function mockFetchPaginated(data: unknown[], status = 200) { let callCount = 0; - globalThis.fetch = spy(() => { + globalThis.fetch = vi.fn(() => { callCount++; const items = callCount === 1 ? data : []; const body = { @@ -41,7 +47,7 @@ function mockFetchPaginated(data: unknown[], status = 200) { } function mockFetchError(status: number, statusText: string) { - globalThis.fetch = spy(() => + globalThis.fetch = vi.fn(() => Promise.resolve( new Response(JSON.stringify({ error: statusText }), { status, @@ -145,9 +151,9 @@ describe("BetterstackClient", () => { test("sends correct auth header", async () => { mockFetchPaginated(MOCK_MONITORS); await client.getMonitors(); - const fetchMock = globalThis.fetch as unknown as ReturnType; - assertSpyCalls(fetchMock, 1); - const [url, options] = fetchMock.calls[0].args as [string, RequestInit]; + const fetchMock = globalThis.fetch as unknown as Mock; + expect(fetchMock).toHaveBeenCalledTimes(1); + const [url, options] = fetchMock.mock.calls[0] as [string, RequestInit]; expect(url).toBe("https://uptime.betterstack.com/api/v2/monitors"); expect((options.headers as Record).Authorization).toBe( "Bearer test-api-key", @@ -156,7 +162,7 @@ describe("BetterstackClient", () => { test("follows pagination.next for multiple pages", async () => { let callCount = 0; - globalThis.fetch = spy(() => { + globalThis.fetch = vi.fn(() => { callCount++; const body = callCount === 1 diff --git a/packages/importers/src/providers/betterstack/mapper.test.ts b/packages/importers/src/providers/betterstack/mapper.test.ts index f514fe93b..6d7a483c0 100644 --- a/packages/importers/src/providers/betterstack/mapper.test.ts +++ b/packages/importers/src/providers/betterstack/mapper.test.ts @@ -1,6 +1,5 @@ import { regionDict } from "@openstatus/regions"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { MOCK_INCIDENTS, diff --git a/packages/importers/src/providers/betterstack/provider.test.ts b/packages/importers/src/providers/betterstack/provider.test.ts index 28aa05453..f37d1142e 100644 --- a/packages/importers/src/providers/betterstack/provider.test.ts +++ b/packages/importers/src/providers/betterstack/provider.test.ts @@ -1,6 +1,4 @@ -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { spy } from "@std/testing/mock"; +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { MOCK_INCIDENTS, @@ -26,7 +24,7 @@ function makePaginated(data: unknown[]) { } function setupMockFetch() { - globalThis.fetch = spy((url: string) => { + globalThis.fetch = vi.fn((url: string) => { const path = new URL(url as string).pathname; let body: unknown; @@ -88,7 +86,7 @@ describe("BetterstackProvider", () => { }); test("validate returns friendly 401 error", async () => { - globalThis.fetch = spy(() => + globalThis.fetch = vi.fn(() => Promise.resolve( new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, diff --git a/packages/importers/src/providers/checkly/client.test.ts b/packages/importers/src/providers/checkly/client.test.ts index 0f4277d55..98b3e3d28 100644 --- a/packages/importers/src/providers/checkly/client.test.ts +++ b/packages/importers/src/providers/checkly/client.test.ts @@ -1,6 +1,12 @@ -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, spy } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type Mock, +} from "vitest"; import { createChecklyClient } from "./client"; import { @@ -23,7 +29,7 @@ function jsonResponse(body: unknown, status = 200) { // Returns each body in order; repeats the last one once exhausted. function mockFetchSequence(bodies: unknown[], status = 200) { let call = 0; - globalThis.fetch = spy(() => { + globalThis.fetch = vi.fn(() => { const body = bodies[Math.min(call, bodies.length - 1)]; call++; return Promise.resolve(jsonResponse(body, status)); @@ -82,9 +88,9 @@ describe("ChecklyClient", () => { test("sends both auth headers and correct path", async () => { mockFetchSequence([MOCK_CHECKS]); await client.getChecks(); - const fetchMock = globalThis.fetch as unknown as ReturnType; - assertSpyCalls(fetchMock, 1); - const [url, options] = fetchMock.calls[0].args as [string, RequestInit]; + const fetchMock = globalThis.fetch as unknown as Mock; + expect(fetchMock).toHaveBeenCalledTimes(1); + const [url, options] = fetchMock.mock.calls[0] as [string, RequestInit]; expect(url).toContain("https://api.checklyhq.com/v1/checks"); const headers = options.headers as Record; expect(headers.Authorization).toBe("Bearer test-api-key"); @@ -103,9 +109,9 @@ describe("ChecklyClient", () => { ]); const pages = await client.getStatusPages(); expect(pages).toHaveLength(2); - const fetchMock = globalThis.fetch as unknown as ReturnType; - assertSpyCalls(fetchMock, 2); - const secondUrl = fetchMock.calls[1].args[0] as string; + const fetchMock = globalThis.fetch as unknown as Mock; + expect(fetchMock).toHaveBeenCalledTimes(2); + const secondUrl = fetchMock.mock.calls[1][0] as string; expect(secondUrl).toContain("nextId=cursor-2"); }); @@ -117,8 +123,8 @@ describe("ChecklyClient", () => { mockFetchSequence([fullPage, []]); const checks = await client.getChecks(); expect(checks).toHaveLength(100); - const fetchMock = globalThis.fetch as unknown as ReturnType; - assertSpyCalls(fetchMock, 2); - expect(fetchMock.calls[1].args[0] as string).toContain("page=2"); + const fetchMock = globalThis.fetch as unknown as Mock; + expect(fetchMock).toHaveBeenCalledTimes(2); + expect(fetchMock.mock.calls[1][0] as string).toContain("page=2"); }); }); diff --git a/packages/importers/src/providers/checkly/mapper.test.ts b/packages/importers/src/providers/checkly/mapper.test.ts index 5c32a5e6b..c2d6610b0 100644 --- a/packages/importers/src/providers/checkly/mapper.test.ts +++ b/packages/importers/src/providers/checkly/mapper.test.ts @@ -1,6 +1,5 @@ import { regionDict } from "@openstatus/regions"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { MOCK_CHECKS, diff --git a/packages/importers/src/providers/checkly/provider.test.ts b/packages/importers/src/providers/checkly/provider.test.ts index 52f9511ed..3171a3b1a 100644 --- a/packages/importers/src/providers/checkly/provider.test.ts +++ b/packages/importers/src/providers/checkly/provider.test.ts @@ -1,6 +1,4 @@ -import { expect } from "@std/expect"; -import { afterEach, describe, test } from "@std/testing/bdd"; -import { spy } from "@std/testing/mock"; +import { afterEach, describe, expect, test, vi } from "vitest"; import type { PhaseResult } from "../../types"; import { @@ -22,7 +20,7 @@ function jsonResponse(body: unknown, status = 200) { } function mockFetchByPath(status = 200) { - globalThis.fetch = spy((input: string | URL | Request) => { + globalThis.fetch = vi.fn((input: string | URL | Request) => { const url = String(input); let body: unknown = []; // incidents must be matched before the status-pages prefix diff --git a/packages/importers/src/providers/instatus/client.test.ts b/packages/importers/src/providers/instatus/client.test.ts index df139d124..076b0dba5 100644 --- a/packages/importers/src/providers/instatus/client.test.ts +++ b/packages/importers/src/providers/instatus/client.test.ts @@ -1,6 +1,12 @@ -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { spy } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type Mock, +} from "vitest"; import { createInstatusClient } from "./client"; import { @@ -14,7 +20,7 @@ import { const originalFetch = globalThis.fetch; function mockFetch(data: unknown, status = 200) { - globalThis.fetch = spy(() => + globalThis.fetch = vi.fn(() => Promise.resolve( new Response(JSON.stringify(data), { status, @@ -27,7 +33,7 @@ function mockFetch(data: unknown, status = 200) { function mockFetchPaginated(data: unknown, status = 200) { let callCount = 0; - globalThis.fetch = spy(() => { + globalThis.fetch = vi.fn(() => { callCount++; const body = callCount === 1 ? data : []; return Promise.resolve( @@ -124,8 +130,8 @@ describe("InstatusClient", () => { test("sends correct Bearer auth header", async () => { mockFetchPaginated(MOCK_PAGES); await client.getPages(); - const fetchMock = globalThis.fetch as unknown as ReturnType; - const [url, options] = fetchMock.calls[0].args as [string, RequestInit]; + const fetchMock = globalThis.fetch as unknown as Mock; + const [url, options] = fetchMock.mock.calls[0] as [string, RequestInit]; expect(url).toBe("https://api.instatus.com/v2/pages?page=1&per_page=100"); expect((options.headers as Record).Authorization).toBe( "Bearer test-api-key", @@ -141,7 +147,7 @@ describe("InstatusClient", () => { })); let callCount = 0; - globalThis.fetch = spy(() => { + globalThis.fetch = vi.fn(() => { callCount++; // First call: 100 items (= per_page), second call: empty array const body = callCount === 1 ? items : []; @@ -162,8 +168,8 @@ describe("InstatusClient", () => { test("uses v1 for incidents and v2 for other endpoints", async () => { mockFetchPaginated(MOCK_INCIDENTS); await client.getIncidents("in_page_001"); - const fetchMock = globalThis.fetch as unknown as ReturnType; - const [url] = fetchMock.calls[0].args as [string, RequestInit]; + const fetchMock = globalThis.fetch as unknown as Mock; + const [url] = fetchMock.mock.calls[0] as [string, RequestInit]; expect(url).toContain("/v1/in_page_001/incidents"); }); }); diff --git a/packages/importers/src/providers/instatus/mapper.test.ts b/packages/importers/src/providers/instatus/mapper.test.ts index a451afcf6..f8931d730 100644 --- a/packages/importers/src/providers/instatus/mapper.test.ts +++ b/packages/importers/src/providers/instatus/mapper.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { MOCK_COMPONENTS, diff --git a/packages/importers/src/providers/instatus/provider.test.ts b/packages/importers/src/providers/instatus/provider.test.ts index 0b904b362..541677db0 100644 --- a/packages/importers/src/providers/instatus/provider.test.ts +++ b/packages/importers/src/providers/instatus/provider.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, it } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { MOCK_COMPONENTS, diff --git a/packages/importers/src/providers/statuspage/client.test.ts b/packages/importers/src/providers/statuspage/client.test.ts index a75fcd5c2..44fccc744 100644 --- a/packages/importers/src/providers/statuspage/client.test.ts +++ b/packages/importers/src/providers/statuspage/client.test.ts @@ -1,6 +1,12 @@ -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, spy } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type Mock, +} from "vitest"; import { createStatuspageClient } from "./client"; import { @@ -14,7 +20,7 @@ import { const originalFetch = globalThis.fetch; function mockFetch(data: unknown, status = 200) { - globalThis.fetch = spy(() => + globalThis.fetch = vi.fn(() => Promise.resolve( new Response(JSON.stringify(data), { status, @@ -30,7 +36,7 @@ function mockFetch(data: unknown, status = 200) { */ function mockFetchPaginated(data: unknown, status = 200) { let callCount = 0; - globalThis.fetch = spy(() => { + globalThis.fetch = vi.fn(() => { callCount++; const body = callCount === 1 ? data : []; return Promise.resolve( @@ -223,8 +229,8 @@ describe("StatuspageClient", () => { test("getPage calls correct URL path", async () => { mockFetch(MOCK_PAGES[0]); await client.getPage("sp_page_001"); - const fetchMock = globalThis.fetch as unknown as ReturnType; - const [url] = fetchMock.calls[0].args as [string, RequestInit]; + const fetchMock = globalThis.fetch as unknown as Mock; + const [url] = fetchMock.mock.calls[0] as [string, RequestInit]; expect(url).toBe("https://api.statuspage.io/v1/pages/sp_page_001"); }); @@ -243,9 +249,9 @@ describe("StatuspageClient", () => { test("sends correct auth header", async () => { mockFetch(MOCK_PAGES); await client.getPages(); - const fetchMock = globalThis.fetch as unknown as ReturnType; - assertSpyCalls(fetchMock, 1); - const [url, options] = fetchMock.calls[0].args as [string, RequestInit]; + const fetchMock = globalThis.fetch as unknown as Mock; + expect(fetchMock).toHaveBeenCalledTimes(1); + const [url, options] = fetchMock.mock.calls[0] as [string, RequestInit]; expect(url).toBe("https://api.statuspage.io/v1/pages"); expect((options.headers as Record).Authorization).toBe( "OAuth test-api-key", diff --git a/packages/importers/src/providers/statuspage/mapper.test.ts b/packages/importers/src/providers/statuspage/mapper.test.ts index dabfc431c..c2d13da2e 100644 --- a/packages/importers/src/providers/statuspage/mapper.test.ts +++ b/packages/importers/src/providers/statuspage/mapper.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { MOCK_COMPONENTS, diff --git a/packages/importers/src/providers/statuspage/provider.test.ts b/packages/importers/src/providers/statuspage/provider.test.ts index 3f314f054..d4f5de167 100644 --- a/packages/importers/src/providers/statuspage/provider.test.ts +++ b/packages/importers/src/providers/statuspage/provider.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, it } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { MOCK_COMPONENTS, diff --git a/packages/importers/src/schemas.test.ts b/packages/importers/src/schemas.test.ts index 7559f30fe..8e62e7227 100644 --- a/packages/importers/src/schemas.test.ts +++ b/packages/importers/src/schemas.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { lenientEnum } from "./schemas"; diff --git a/packages/importers/tsconfig.json b/packages/importers/tsconfig.json index 42e929db7..22dfcb384 100644 --- a/packages/importers/tsconfig.json +++ b/packages/importers/tsconfig.json @@ -4,14 +4,5 @@ "outDir": "./dist", "rootDir": "./src" }, - "include": ["src"], - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + "include": ["src"] } diff --git a/packages/importers/vitest.config.ts b/packages/importers/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/importers/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/locales/tsconfig.json b/packages/locales/tsconfig.json index 84b911316..24e4e0f01 100644 --- a/packages/locales/tsconfig.json +++ b/packages/locales/tsconfig.json @@ -1,13 +1,4 @@ { "extends": "@openstatus/tsconfig/base.json", - "include": ["*.ts"], - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + "include": ["*.ts"] } diff --git a/packages/notifications/base/package.json b/packages/notifications/base/package.json index ae923362c..001109d67 100644 --- a/packages/notifications/base/package.json +++ b/packages/notifications/base/package.json @@ -8,7 +8,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", + "test": "vitest run", "tsc": "tsc" }, "dependencies": { @@ -16,10 +16,10 @@ "@openstatus/regions": "workspace:*" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/notifications/base/src/utils/duration.test.ts b/packages/notifications/base/src/utils/duration.test.ts index cd99abb7f..557da289f 100644 --- a/packages/notifications/base/src/utils/duration.test.ts +++ b/packages/notifications/base/src/utils/duration.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { calculateDuration, formatDuration } from "./duration"; diff --git a/packages/notifications/base/src/utils/incident.test.ts b/packages/notifications/base/src/utils/incident.test.ts index a3d3f9c67..1d4ac117c 100644 --- a/packages/notifications/base/src/utils/incident.test.ts +++ b/packages/notifications/base/src/utils/incident.test.ts @@ -1,6 +1,5 @@ import type { MonitorIncident } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { getIncidentDuration } from "./incident"; diff --git a/packages/notifications/base/src/utils/timestamp.test.ts b/packages/notifications/base/src/utils/timestamp.test.ts index 08a0793f1..ddeec454c 100644 --- a/packages/notifications/base/src/utils/timestamp.test.ts +++ b/packages/notifications/base/src/utils/timestamp.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { formatTimestamp } from "./timestamp"; diff --git a/packages/notifications/base/tsconfig.json b/packages/notifications/base/tsconfig.json index a74701aa0..fc180ffce 100644 --- a/packages/notifications/base/tsconfig.json +++ b/packages/notifications/base/tsconfig.json @@ -1,12 +1,5 @@ { "extends": "@openstatus/tsconfig/base.json", "include": ["src", "*.ts"], - "exclude": [ - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + "exclude": ["**/*.test.ts"] } diff --git a/packages/notifications/base/vitest.config.ts b/packages/notifications/base/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/notifications/base/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/notifications/bird-whatsapp/package.json b/packages/notifications/bird-whatsapp/package.json index ed1a0b79b..85d586483 100644 --- a/packages/notifications/bird-whatsapp/package.json +++ b/packages/notifications/bird-whatsapp/package.json @@ -4,7 +4,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", + "test": "vitest run", "tsc": "tsc" }, "dependencies": { @@ -14,10 +14,10 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/notifications/bird-whatsapp/src/index.test.ts b/packages/notifications/bird-whatsapp/src/index.test.ts index ea2039ae9..146f9937f 100644 --- a/packages/notifications/bird-whatsapp/src/index.test.ts +++ b/packages/notifications/bird-whatsapp/src/index.test.ts @@ -1,8 +1,14 @@ import type { Monitor } from "@openstatus/db/src/schema"; import { selectNotificationSchema } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, type Stub, stub } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { sendAlert, sendDegraded, sendRecovery, sendTest } from "./index"; @@ -27,7 +33,7 @@ function createNotification() { }); } -function parseRequestBody(call: Stub): { +function parseRequestBody(call: MockInstance): { messageRequests: Array<{ receiver: { contacts: Array<{ identifierKey: string; identifierValue: string }>; @@ -40,21 +46,23 @@ function parseRequestBody(call: Stub): { }; }>; } { - const body = call.calls[0].args[1]?.body; + const body = call.mock.calls[0][1]?.body; return JSON.parse(body as string); } describe("WhatsApp Notifications (Bird)", () => { - let fetchMock: Stub; + let fetchMock: MockInstance; beforeEach(() => { - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 200 })), - ); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 200 })), + ); }); afterEach(() => { - fetchMock.restore(); + fetchMock.mockRestore(); }); test("sendAlert calls Bird API with alert template", async () => { @@ -66,11 +74,11 @@ describe("WhatsApp Notifications (Bird)", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const url = fetchMock.calls[0].args[0] as string; + expect(fetchMock).toHaveBeenCalledTimes(1); + const url = fetchMock.mock.calls[0][0] as string; expect(url).toMatch(BIRD_URL_PATTERN); - const options = fetchMock.calls[0].args[1] as RequestInit; + const options = fetchMock.mock.calls[0][1] as RequestInit; expect(options.headers).toHaveProperty("Authorization"); expect((options.headers as Record).Authorization).toMatch( /^AccessKey /, @@ -107,7 +115,7 @@ describe("WhatsApp Notifications (Bird)", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); const body = parseRequestBody(fetchMock); expect(body.messageRequests[0].template.projectId).toBe( "2068001e-330d-4093-8d06-7071e9d34aa1", @@ -129,7 +137,7 @@ describe("WhatsApp Notifications (Bird)", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); const body = parseRequestBody(fetchMock); expect(body.messageRequests[0].template.projectId).toBe( "c9a842d9-cf9e-4a13-8cde-024d57e29143", @@ -145,7 +153,7 @@ describe("WhatsApp Notifications (Bird)", () => { test("sendTest calls Bird API with test template and openstat.us URL", async () => { await sendTest({ phoneNumber: "+33623456789" }); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); const body = parseRequestBody(fetchMock); expect(body.messageRequests[0].receiver.contacts[0].identifierValue).toBe( "+33623456789", diff --git a/packages/notifications/bird-whatsapp/tsconfig.json b/packages/notifications/bird-whatsapp/tsconfig.json index 76722cdc6..48baa5cfe 100644 --- a/packages/notifications/bird-whatsapp/tsconfig.json +++ b/packages/notifications/bird-whatsapp/tsconfig.json @@ -3,14 +3,5 @@ "include": ["src", "*.ts"], "compilerOptions": { "resolveJsonModule": true - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/notifications/bird-whatsapp/vitest.config.ts b/packages/notifications/bird-whatsapp/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/notifications/bird-whatsapp/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/notifications/discord/package.json b/packages/notifications/discord/package.json index 91917cdce..c761eabf8 100644 --- a/packages/notifications/discord/package.json +++ b/packages/notifications/discord/package.json @@ -4,7 +4,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", + "test": "vitest run", "tsc": "tsc" }, "dependencies": { @@ -14,10 +14,10 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/notifications/discord/src/index.test.ts b/packages/notifications/discord/src/index.test.ts index 72e7baa7e..40cac8ea2 100644 --- a/packages/notifications/discord/src/index.test.ts +++ b/packages/notifications/discord/src/index.test.ts @@ -1,8 +1,14 @@ import { selectNotificationSchema } from "@openstatus/db/src/schema"; import { COLOR_DECIMALS } from "@openstatus/notification-base"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, type Stub, stub } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { buildAlertEmbed, @@ -20,14 +26,14 @@ const ok = (): Promise => Promise.resolve(new Response(null, { status: 200 })); describe("Discord Notifications", () => { - let fetchMock: Stub; + let fetchMock: MockInstance; beforeEach(() => { - fetchMock = stub(globalThis, "fetch", ok); + fetchMock = vi.spyOn(globalThis, "fetch").mockImplementation(ok); }); afterEach(() => { - fetchMock.restore(); + fetchMock.mockRestore(); }); const createMockMonitor = () => ({ @@ -67,8 +73,8 @@ describe("Discord Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe( "https://discord.com/api/webhooks/123456789/abcdefgh", ); @@ -99,8 +105,8 @@ describe("Discord Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const body = JSON.parse(callArgs[1].body); expect(body.embeds).toBeDefined(); expect(body.embeds[0].title).toContain("is recovered"); @@ -123,8 +129,8 @@ describe("Discord Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const body = JSON.parse(callArgs[1].body); expect(body.embeds).toBeDefined(); expect(body.embeds[0].title).toContain("is degraded"); @@ -136,8 +142,8 @@ describe("Discord Notifications", () => { const webhookUrl = "https://discord.com/api/webhooks/123456789/abcdefgh"; await sendTestDiscordMessage(webhookUrl); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe(webhookUrl); const body = JSON.parse(callArgs[1].body); expect(body.embeds).toBeDefined(); @@ -145,21 +151,21 @@ describe("Discord Notifications", () => { }); test("Send Test Discord Message with empty webhookUrl", async () => { - expect(sendTestDiscordMessage("")).rejects.toThrow(); - assertSpyCalls(fetchMock, 0); + await expect(sendTestDiscordMessage("")).rejects.toThrow(); + expect(fetchMock).toHaveBeenCalledTimes(0); }); test("Handle fetch error gracefully", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); const monitor = createMockMonitor(); const notification = selectNotificationSchema.parse( createMockNotification(), ); - expect( + await expect( sendAlert({ // @ts-expect-error monitor, diff --git a/packages/notifications/discord/tsconfig.json b/packages/notifications/discord/tsconfig.json index 76722cdc6..48baa5cfe 100644 --- a/packages/notifications/discord/tsconfig.json +++ b/packages/notifications/discord/tsconfig.json @@ -3,14 +3,5 @@ "include": ["src", "*.ts"], "compilerOptions": { "resolveJsonModule": true - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/notifications/discord/vitest.config.ts b/packages/notifications/discord/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/notifications/discord/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/notifications/email/package.json b/packages/notifications/email/package.json index b2c785e5c..e38b3ab22 100644 --- a/packages/notifications/email/package.json +++ b/packages/notifications/email/package.json @@ -5,7 +5,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", + "test": "vitest run", "tsc": "tsc" }, "dependencies": { @@ -25,6 +25,7 @@ "@openstatus/tsconfig": "workspace:*", "@types/node": "catalog:", "@types/react-dom": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/notifications/email/src/index.test.ts b/packages/notifications/email/src/index.test.ts index d567dc17e..e40e420a9 100644 --- a/packages/notifications/email/src/index.test.ts +++ b/packages/notifications/email/src/index.test.ts @@ -3,29 +3,28 @@ import { selectNotificationSchema } from "@openstatus/db/src/schema"; import { EmailClient } from "@openstatus/emails/src/client"; import { afterEach, - assertSpyCalls, beforeEach, describe, expect, - type Stub, - stub, test, -} from "@openstatus/test-utils"; + vi, + type MockInstance, +} from "vitest"; import { sendAlert, sendDegraded, sendRecovery } from "./index"; // biome-ignore lint/suspicious/noExplicitAny: stub over the EmailClient method -let sendMonitorAlertMock: Stub; +let sendMonitorAlertMock: MockInstance; describe("Email Notifications", () => { beforeEach(() => { - sendMonitorAlertMock = stub(EmailClient.prototype, "sendMonitorAlert", () => - Promise.resolve(), - ); + sendMonitorAlertMock = vi + .spyOn(EmailClient.prototype, "sendMonitorAlert") + .mockImplementation(() => Promise.resolve()); }); afterEach(() => { - sendMonitorAlertMock.restore(); + sendMonitorAlertMock.mockRestore(); }); const createMockMonitor = () => ({ @@ -67,8 +66,8 @@ describe("Email Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(sendMonitorAlertMock, 1); - const callArgs = sendMonitorAlertMock.calls[0].args[0]; + expect(sendMonitorAlertMock).toHaveBeenCalledTimes(1); + const callArgs = sendMonitorAlertMock.mock.calls[0][0]; expect(callArgs.name).toBe("API Health Check"); expect(callArgs.type).toBe("alert"); expect(callArgs.to).toBe("ping@openstatus.dev"); @@ -94,8 +93,8 @@ describe("Email Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(sendMonitorAlertMock, 1); - const callArgs = sendMonitorAlertMock.calls[0].args[0]; + expect(sendMonitorAlertMock).toHaveBeenCalledTimes(1); + const callArgs = sendMonitorAlertMock.mock.calls[0][0]; expect(callArgs.status).toBeUndefined(); expect(callArgs.latency).toBe("N/A"); expect(callArgs.region).toBe("N/A"); @@ -117,8 +116,8 @@ describe("Email Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(sendMonitorAlertMock, 1); - const callArgs = sendMonitorAlertMock.calls[0].args[0]; + expect(sendMonitorAlertMock).toHaveBeenCalledTimes(1); + const callArgs = sendMonitorAlertMock.mock.calls[0][0]; expect(callArgs.type).toBe("recovery"); expect(callArgs.name).toBe("API Health Check"); expect(callArgs.to).toBe("ping@openstatus.dev"); @@ -143,8 +142,8 @@ describe("Email Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(sendMonitorAlertMock, 1); - const callArgs = sendMonitorAlertMock.calls[0].args[0]; + expect(sendMonitorAlertMock).toHaveBeenCalledTimes(1); + const callArgs = sendMonitorAlertMock.mock.calls[0][0]; expect(callArgs.type).toBe("degraded"); expect(callArgs.name).toBe("API Health Check"); expect(callArgs.status).toBe("503"); @@ -170,8 +169,8 @@ describe("Email Notifications", () => { incident: { startedAt }, }); - assertSpyCalls(sendMonitorAlertMock, 1); - const callArgs = sendMonitorAlertMock.calls[0].args[0]; + expect(sendMonitorAlertMock).toHaveBeenCalledTimes(1); + const callArgs = sendMonitorAlertMock.mock.calls[0][0]; expect(callArgs.degradedAfter).toBe(250); expect(callArgs.firstSeen).toBe(startedAt.toISOString()); }); @@ -196,6 +195,6 @@ describe("Email Notifications", () => { }); // Should not call sendMonitorAlert when data is invalid - assertSpyCalls(sendMonitorAlertMock, 0); + expect(sendMonitorAlertMock).toHaveBeenCalledTimes(0); }); }); diff --git a/packages/notifications/email/src/test-preload.ts b/packages/notifications/email/src/test-preload.ts index 3e4c5cf31..e7cc1bb4a 100644 --- a/packages/notifications/email/src/test-preload.ts +++ b/packages/notifications/email/src/test-preload.ts @@ -2,8 +2,7 @@ * Test env setup. Import this FIRST so env vars are set before module-level * env validations fire on import. * - * Bun's test runner set NODE_ENV=test automatically; Deno does not. This - * package's env skips validation when NODE_ENV=test, and @openstatus/emails + * This package's env skips validation when NODE_ENV=test, and @openstatus/emails * validates RESEND_API_KEY at module load. */ process.env.NODE_ENV = "test"; diff --git a/packages/notifications/email/tsconfig.json b/packages/notifications/email/tsconfig.json index 76722cdc6..48baa5cfe 100644 --- a/packages/notifications/email/tsconfig.json +++ b/packages/notifications/email/tsconfig.json @@ -3,14 +3,5 @@ "include": ["src", "*.ts"], "compilerOptions": { "resolveJsonModule": true - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/notifications/email/vitest.config.ts b/packages/notifications/email/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/notifications/email/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/notifications/google-chat/package.json b/packages/notifications/google-chat/package.json index 5fd6df27d..14477c5d0 100644 --- a/packages/notifications/google-chat/package.json +++ b/packages/notifications/google-chat/package.json @@ -4,7 +4,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", + "test": "vitest run", "tsc": "tsc" }, "dependencies": { @@ -14,10 +14,10 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/notifications/google-chat/src/index.test.ts b/packages/notifications/google-chat/src/index.test.ts index 6a84bffdd..909d82904 100644 --- a/packages/notifications/google-chat/src/index.test.ts +++ b/packages/notifications/google-chat/src/index.test.ts @@ -1,21 +1,29 @@ import { selectNotificationSchema } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, stub, type Stub } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { sendAlert, sendDegraded, sendRecovery, sendTest } from "./index"; describe("Google Chat Notifications", () => { - let fetchMock: Stub; + let fetchMock: MockInstance; beforeEach(() => { - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 200 })), - ); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 200 })), + ); }); afterEach(() => { - fetchMock.restore(); + fetchMock.mockRestore(); }); const createMockMonitor = () => ({ @@ -55,8 +63,8 @@ describe("Google Chat Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe( "https://google.com/api/webhooks/123456789/abcdefgh", ); @@ -85,8 +93,8 @@ describe("Google Chat Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const body = JSON.parse(callArgs[1].body); expect(body.text).toContain("✅ Recovered"); expect(body.text).toContain("API Health Check"); @@ -107,8 +115,8 @@ describe("Google Chat Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const body = JSON.parse(callArgs[1].body); expect(body.text).toContain("⚠️ Degraded"); expect(body.text).toContain("API Health Check"); @@ -120,8 +128,8 @@ describe("Google Chat Notifications", () => { const result = await sendTest(webhookUrl); expect(result).toBe(true); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe(webhookUrl); const body = JSON.parse(callArgs[1].body); expect(body.text).toContain("🧪 Test"); @@ -132,20 +140,20 @@ describe("Google Chat Notifications", () => { const result = await sendTest(""); expect(result).toBe(false); - assertSpyCalls(fetchMock, 0); + expect(fetchMock).toHaveBeenCalledTimes(0); }); test("Handle fetch error gracefully", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); const monitor = createMockMonitor(); const notification = selectNotificationSchema.parse( createMockNotification(), ); - expect( + await expect( sendAlert({ // @ts-expect-error monitor, diff --git a/packages/notifications/google-chat/tsconfig.json b/packages/notifications/google-chat/tsconfig.json index 76722cdc6..48baa5cfe 100644 --- a/packages/notifications/google-chat/tsconfig.json +++ b/packages/notifications/google-chat/tsconfig.json @@ -3,14 +3,5 @@ "include": ["src", "*.ts"], "compilerOptions": { "resolveJsonModule": true - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/notifications/google-chat/vitest.config.ts b/packages/notifications/google-chat/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/notifications/google-chat/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/notifications/grafana-oncall/package.json b/packages/notifications/grafana-oncall/package.json index 137d8931e..e220799f6 100644 --- a/packages/notifications/grafana-oncall/package.json +++ b/packages/notifications/grafana-oncall/package.json @@ -4,7 +4,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", + "test": "vitest run", "tsc": "tsc" }, "dependencies": { @@ -14,10 +14,10 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/notifications/grafana-oncall/src/index.test.ts b/packages/notifications/grafana-oncall/src/index.test.ts index 5676f5c72..c0a5512a2 100644 --- a/packages/notifications/grafana-oncall/src/index.test.ts +++ b/packages/notifications/grafana-oncall/src/index.test.ts @@ -1,21 +1,29 @@ import { selectNotificationSchema } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, stub, type Stub } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { sendAlert, sendDegraded, sendRecovery, sendTest } from "./index"; describe("Grafana OnCall Notifications", () => { - let fetchMock: Stub; + let fetchMock: MockInstance; beforeEach(() => { - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 200 })), - ); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 200 })), + ); }); afterEach(() => { - fetchMock.restore(); + fetchMock.mockRestore(); }); const createMockMonitor = () => ({ @@ -55,8 +63,8 @@ describe("Grafana OnCall Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe( "https://oncall.example.com/integrations/v1/webhook/abc123", ); @@ -87,8 +95,8 @@ describe("Grafana OnCall Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const body = JSON.parse(callArgs[1].body); expect(body.message).toBe("Status code: 503"); }); @@ -108,8 +116,8 @@ describe("Grafana OnCall Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe( "https://oncall.example.com/integrations/v1/webhook/abc123", ); @@ -136,8 +144,8 @@ describe("Grafana OnCall Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe( "https://oncall.example.com/integrations/v1/webhook/abc123", ); @@ -156,8 +164,8 @@ describe("Grafana OnCall Notifications", () => { const result = await sendTest({ webhookUrl }); expect(result).toBe(true); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe(webhookUrl); expect(callArgs[1].method).toBe("POST"); @@ -170,36 +178,38 @@ describe("Grafana OnCall Notifications", () => { }); test("Send Test returns false on network error", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); const result = await sendTest({ webhookUrl: "https://oncall.example.com/integrations/v1/webhook/test123", }); expect(result).toBe(false); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); test("Send Test returns false on non-ok response", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 401 })), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 401 })), + ); const result = await sendTest({ webhookUrl: "https://oncall.example.com/integrations/v1/webhook/test123", }); expect(result).toBe(false); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); test("Send Alert throws on non-ok response", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => + fetchMock.mockRestore(); + fetchMock = vi.spyOn(globalThis, "fetch").mockImplementation(() => Promise.resolve( new Response(null, { status: 500, @@ -213,7 +223,7 @@ describe("Grafana OnCall Notifications", () => { createMockNotification(), ); - expect( + await expect( sendAlert({ // @ts-expect-error monitor, @@ -226,10 +236,10 @@ describe("Grafana OnCall Notifications", () => { }); test("Handle fetch error gracefully", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); const monitor = createMockMonitor(); const notification = selectNotificationSchema.parse( @@ -247,6 +257,6 @@ describe("Grafana OnCall Notifications", () => { }), ).rejects.toThrow(); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/notifications/grafana-oncall/tsconfig.json b/packages/notifications/grafana-oncall/tsconfig.json index 76722cdc6..48baa5cfe 100644 --- a/packages/notifications/grafana-oncall/tsconfig.json +++ b/packages/notifications/grafana-oncall/tsconfig.json @@ -3,14 +3,5 @@ "include": ["src", "*.ts"], "compilerOptions": { "resolveJsonModule": true - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/notifications/grafana-oncall/vitest.config.ts b/packages/notifications/grafana-oncall/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/notifications/grafana-oncall/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/notifications/ms-teams/package.json b/packages/notifications/ms-teams/package.json index 54f5a0eb9..5675dda39 100644 --- a/packages/notifications/ms-teams/package.json +++ b/packages/notifications/ms-teams/package.json @@ -4,7 +4,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", + "test": "vitest run", "tsc": "tsc" }, "dependencies": { @@ -14,10 +14,10 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/notifications/ms-teams/src/index.test.ts b/packages/notifications/ms-teams/src/index.test.ts index 18b41f49b..1badbbd4e 100644 --- a/packages/notifications/ms-teams/src/index.test.ts +++ b/packages/notifications/ms-teams/src/index.test.ts @@ -1,12 +1,18 @@ import type { MonitorIncident, Monitor } from "@openstatus/db/src/schema"; import { selectNotificationSchema } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, stub, type Stub } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { sendAlert, sendDegraded, sendRecovery, sendTest } from "./index"; -type FetchMock = Stub; +type FetchMock = MockInstance; type AdaptiveCardElement = { type: string; @@ -42,13 +48,15 @@ describe("Microsoft Teams Notifications", () => { let fetchMock: FetchMock; beforeEach(() => { - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 200 })), - ); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 200 })), + ); }); afterEach(() => { - fetchMock.restore(); + fetchMock.mockRestore(); }); const createMockMonitor = (): Monitor => ({ @@ -116,8 +124,7 @@ describe("Microsoft Teams Notifications", () => { }); const readMessagePayload = (): MessagePayload => { - const calls = fetchMock.calls; - const init = calls[0].args[1]; + const init = fetchMock.mock.calls[0]?.[1]; if (!init || typeof init.body !== "string") { throw new Error("expected string body"); } @@ -171,8 +178,8 @@ describe("Microsoft Teams Notifications", () => { regions: ["iad"], }); - assertSpyCalls(fetchMock, 1); - const [url, init] = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const [url, init] = fetchMock.mock.calls[0]; expect(String(url)).toContain("logic.azure.com"); expect(init?.method).toBe("POST"); @@ -304,7 +311,7 @@ describe("Microsoft Teams Notifications", () => { "https://prod-00.westeurope.logic.azure.com:443/workflows/test/triggers/manual/paths/invoke", }); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); const card = getCard(readMessagePayload()); const header = findContainerByStyle(card, "good"); expect(header?.items?.[0]?.text).toBe("Test notification"); @@ -313,14 +320,14 @@ describe("Microsoft Teams Notifications", () => { test("Send Test with empty webhookUrl", async () => { await expect(sendTest({ webhookUrl: "" })).rejects.toThrow(); - assertSpyCalls(fetchMock, 0); + expect(fetchMock).toHaveBeenCalledTimes(0); }); test("Handle fetch error", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); const monitor = createMockMonitor(); const notification = selectNotificationSchema.parse( diff --git a/packages/notifications/ms-teams/tsconfig.json b/packages/notifications/ms-teams/tsconfig.json index 76722cdc6..48baa5cfe 100644 --- a/packages/notifications/ms-teams/tsconfig.json +++ b/packages/notifications/ms-teams/tsconfig.json @@ -3,14 +3,5 @@ "include": ["src", "*.ts"], "compilerOptions": { "resolveJsonModule": true - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/notifications/ms-teams/vitest.config.ts b/packages/notifications/ms-teams/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/notifications/ms-teams/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/notifications/ntfy/package.json b/packages/notifications/ntfy/package.json index 68637e3d8..d7f88b0ab 100644 --- a/packages/notifications/ntfy/package.json +++ b/packages/notifications/ntfy/package.json @@ -4,7 +4,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", + "test": "vitest run", "tsc": "tsc" }, "dependencies": { @@ -14,10 +14,10 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/notifications/ntfy/src/index.test.ts b/packages/notifications/ntfy/src/index.test.ts index a46f9284c..c10aae59f 100644 --- a/packages/notifications/ntfy/src/index.test.ts +++ b/packages/notifications/ntfy/src/index.test.ts @@ -1,21 +1,29 @@ import { selectNotificationSchema } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, stub, type Stub } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { sendAlert, sendDegraded, sendRecovery, sendTest } from "./index"; describe("Ntfy Notifications", () => { - let fetchMock: Stub; + let fetchMock: MockInstance; beforeEach(() => { - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 200 })), - ); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 200 })), + ); }); afterEach(() => { - fetchMock.restore(); + fetchMock.mockRestore(); }); const createMockMonitor = () => ({ @@ -70,8 +78,8 @@ describe("Ntfy Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe("https://ntfy.sh/my-topic"); expect(callArgs[1].method).toBe("post"); expect(callArgs[1].body).toContain("API Health Check"); @@ -94,8 +102,8 @@ describe("Ntfy Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe("https://ntfy.example.com/my-topic"); }); @@ -114,8 +122,8 @@ describe("Ntfy Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[1].headers.Authorization).toBe("Bearer test-token-123"); }); @@ -133,8 +141,8 @@ describe("Ntfy Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[1].body).toContain("error: Connection timeout"); }); @@ -153,8 +161,8 @@ describe("Ntfy Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[1].body).toContain("is up again"); }); @@ -171,8 +179,8 @@ describe("Ntfy Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[1].headers.Authorization).toBe("Bearer test-token-123"); }); @@ -191,8 +199,8 @@ describe("Ntfy Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[1].body).toContain("is degraded"); }); @@ -202,8 +210,8 @@ describe("Ntfy Notifications", () => { }); expect(result).toBe(true); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe("https://ntfy.sh/test-topic"); expect(callArgs[1].body).toBe("This is a test message from OpenStatus"); expect(callArgs[1].headers).not.toHaveProperty("Authorization"); @@ -216,8 +224,8 @@ describe("Ntfy Notifications", () => { }); expect(result).toBe(true); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe("https://ntfy.example.com/test-topic"); }); @@ -228,16 +236,16 @@ describe("Ntfy Notifications", () => { }); expect(result).toBe(true); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[1].headers.Authorization).toBe("Bearer test-token"); }); test("Handle fetch error gracefully", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); const monitor = createMockMonitor(); const notification = selectNotificationSchema.parse( @@ -256,20 +264,20 @@ describe("Ntfy Notifications", () => { }), ).rejects.toThrow(); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); test("Send Test returns false on error", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); const result = await sendTest({ topic: "test-topic", }); expect(result).toBe(false); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/notifications/ntfy/tsconfig.json b/packages/notifications/ntfy/tsconfig.json index 76722cdc6..48baa5cfe 100644 --- a/packages/notifications/ntfy/tsconfig.json +++ b/packages/notifications/ntfy/tsconfig.json @@ -3,14 +3,5 @@ "include": ["src", "*.ts"], "compilerOptions": { "resolveJsonModule": true - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/notifications/ntfy/vitest.config.ts b/packages/notifications/ntfy/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/notifications/ntfy/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/notifications/opsgenie/package.json b/packages/notifications/opsgenie/package.json index fee08931d..1f4f4aa34 100644 --- a/packages/notifications/opsgenie/package.json +++ b/packages/notifications/opsgenie/package.json @@ -4,7 +4,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", + "test": "vitest run", "tsc": "tsc" }, "dependencies": { @@ -16,12 +16,12 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", "@types/react": "catalog:", "@types/react-dom": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/notifications/opsgenie/src/index.test.ts b/packages/notifications/opsgenie/src/index.test.ts index 67712e534..a85480823 100644 --- a/packages/notifications/opsgenie/src/index.test.ts +++ b/packages/notifications/opsgenie/src/index.test.ts @@ -1,21 +1,29 @@ import { selectNotificationSchema } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, type Stub, stub } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { sendAlert, sendDegraded, sendRecovery, sendTest } from "./index"; describe("OpsGenie Notifications", () => { - let fetchMock: Stub; + let fetchMock: MockInstance; beforeEach(() => { - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 200 })), - ); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 200 })), + ); }); afterEach(() => { - fetchMock.restore(); + fetchMock.mockRestore(); }); const createMockMonitor = () => ({ @@ -73,8 +81,8 @@ describe("OpsGenie Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe("https://api.opsgenie.com/v2/alerts"); expect(callArgs[1].method).toBe("POST"); expect(callArgs[1].headers["Content-Type"]).toBe("application/json"); @@ -105,8 +113,8 @@ describe("OpsGenie Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe("https://api.eu.opsgenie.com/v2/alerts"); }); @@ -127,8 +135,8 @@ describe("OpsGenie Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const body = JSON.parse(callArgs[1].body); expect(body.details.severity).toBe("degraded"); expect(body.message).toBe("API Health Check is degraded"); @@ -147,10 +155,12 @@ describe("OpsGenie Notifications", () => { }, ] as const) { test(`Recovery closes the monitor alias in ${region}`, async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 202 })), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 202 })), + ); await sendRecovery({ // @ts-expect-error @@ -163,8 +173,8 @@ describe("OpsGenie Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const [requestUrl, options] = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const [requestUrl, options] = fetchMock.mock.calls[0]; expect(requestUrl).toBe(url); expect(options.method).toBe("POST"); expect(options.headers["Content-Type"]).toBe("application/json"); @@ -174,10 +184,12 @@ describe("OpsGenie Notifications", () => { } test("Recovery rejects an unsuccessful close response", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 503 })), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 503 })), + ); await expect( sendRecovery({ @@ -188,13 +200,15 @@ describe("OpsGenie Notifications", () => { cronTimestamp: Date.now(), }), ).rejects.toThrow("503"); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); test("Recovery propagates a rejected close request", async () => { const error = new Error("Connection reset"); - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => Promise.reject(error)); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(error)); await expect( sendRecovery({ @@ -205,21 +219,21 @@ describe("OpsGenie Notifications", () => { cronTimestamp: Date.now(), }), ).rejects.toBe(error); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); test("Handle fetch error gracefully", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); const monitor = createMockMonitor(); const notification = selectNotificationSchema.parse( createMockNotification(), ); const incident = createMockIncident(); - expect( + await expect( sendAlert({ // @ts-expect-error monitor, @@ -232,14 +246,14 @@ describe("OpsGenie Notifications", () => { }), ).rejects.toThrow(); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); test("Send Test returns false on error", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); const result = await sendTest({ apiKey: "test-api-key", @@ -247,6 +261,6 @@ describe("OpsGenie Notifications", () => { }); expect(result).toBe(false); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/notifications/opsgenie/tsconfig.json b/packages/notifications/opsgenie/tsconfig.json index 76722cdc6..48baa5cfe 100644 --- a/packages/notifications/opsgenie/tsconfig.json +++ b/packages/notifications/opsgenie/tsconfig.json @@ -3,14 +3,5 @@ "include": ["src", "*.ts"], "compilerOptions": { "resolveJsonModule": true - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/notifications/opsgenie/vitest.config.ts b/packages/notifications/opsgenie/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/notifications/opsgenie/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/notifications/pagerduty/package.json b/packages/notifications/pagerduty/package.json index 080b9d03e..36ae8fe55 100644 --- a/packages/notifications/pagerduty/package.json +++ b/packages/notifications/pagerduty/package.json @@ -4,7 +4,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", + "test": "vitest run", "tsc": "tsc" }, "dependencies": { @@ -16,12 +16,12 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", "@types/react": "catalog:", "@types/react-dom": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/notifications/pagerduty/src/index.test.ts b/packages/notifications/pagerduty/src/index.test.ts index a1a94b335..5866915fd 100644 --- a/packages/notifications/pagerduty/src/index.test.ts +++ b/packages/notifications/pagerduty/src/index.test.ts @@ -1,21 +1,29 @@ import { selectNotificationSchema } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, stub, type Stub } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { sendAlert, sendDegraded, sendRecovery, sendTest } from "./index"; describe("PagerDuty Notifications", () => { - let fetchMock: Stub; + let fetchMock: MockInstance; beforeEach(() => { - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 200 })), - ); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 200 })), + ); }); afterEach(() => { - fetchMock.restore(); + fetchMock.mockRestore(); }); const createMockMonitor = () => ({ @@ -68,8 +76,8 @@ describe("PagerDuty Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe("https://events.pagerduty.com/v2/enqueue"); expect(callArgs[1].method).toBe("POST"); @@ -107,9 +115,9 @@ describe("PagerDuty Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 2); - expect(fetchMock.calls[0].args[1].body).toContain("key1"); - expect(fetchMock.calls[1].args[1].body).toContain("key2"); + expect(fetchMock).toHaveBeenCalledTimes(2); + expect(fetchMock.mock.calls[0][1].body).toContain("key1"); + expect(fetchMock.mock.calls[1][1].body).toContain("key2"); }); test("Send Degraded", async () => { @@ -130,8 +138,8 @@ describe("PagerDuty Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const body = JSON.parse(callArgs[1].body); expect(body.payload.summary).toBe("API Health Check is degraded"); expect(body.payload.severity).toBe("warning"); @@ -156,8 +164,8 @@ describe("PagerDuty Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe("https://events.pagerduty.com/v2/enqueue"); const body = JSON.parse(callArgs[1].body); expect(body.routing_key).toBe("my_key"); @@ -171,8 +179,8 @@ describe("PagerDuty Notifications", () => { }); expect(result).toBe(true); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe("https://events.pagerduty.com/v2/enqueue"); expect(callArgs[1].method).toBe("POST"); @@ -186,24 +194,24 @@ describe("PagerDuty Notifications", () => { }); test("Send Test returns false on error", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); const result = await sendTest({ integrationKey: "test-key", }); expect(result).toBe(false); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); test("Handle fetch error gracefully", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); const monitor = createMockMonitor(); const notification = selectNotificationSchema.parse( @@ -211,7 +219,7 @@ describe("PagerDuty Notifications", () => { ); const incident = createMockIncident(); - expect( + await expect( sendAlert({ // @ts-expect-error monitor, @@ -224,6 +232,6 @@ describe("PagerDuty Notifications", () => { }), ).rejects.toThrow(); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/notifications/pagerduty/tsconfig.json b/packages/notifications/pagerduty/tsconfig.json index 76722cdc6..48baa5cfe 100644 --- a/packages/notifications/pagerduty/tsconfig.json +++ b/packages/notifications/pagerduty/tsconfig.json @@ -3,14 +3,5 @@ "include": ["src", "*.ts"], "compilerOptions": { "resolveJsonModule": true - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/notifications/pagerduty/vitest.config.ts b/packages/notifications/pagerduty/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/notifications/pagerduty/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/notifications/slack/package.json b/packages/notifications/slack/package.json index 8e6aacff0..f36a2502b 100644 --- a/packages/notifications/slack/package.json +++ b/packages/notifications/slack/package.json @@ -4,7 +4,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", + "test": "vitest run", "tsc": "tsc" }, "dependencies": { @@ -14,10 +14,10 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/notifications/slack/src/index.test.ts b/packages/notifications/slack/src/index.test.ts index a9b42b712..222e45e4c 100644 --- a/packages/notifications/slack/src/index.test.ts +++ b/packages/notifications/slack/src/index.test.ts @@ -1,8 +1,14 @@ import { selectNotificationSchema } from "@openstatus/db/src/schema"; import { COLORS } from "@openstatus/notification-base"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, type Stub, stub } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { buildAlertBlocks, @@ -18,16 +24,18 @@ import { } from "./index"; describe("Slack Notifications", () => { - let fetchMock: Stub; + let fetchMock: MockInstance; beforeEach(() => { - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 200 })), - ); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 200 })), + ); }); afterEach(() => { - fetchMock.restore(); + fetchMock.mockRestore(); }); const createMockMonitor = () => ({ @@ -67,8 +75,8 @@ describe("Slack Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe("https://hooks.slack.com/services/url"); expect(callArgs[1].method).toBe("POST"); @@ -94,8 +102,8 @@ describe("Slack Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const body = JSON.parse(callArgs[1].body); expect(body.attachments[0].color).toBe(COLORS.red); expect(body.attachments[0].blocks[3].fields[0].text).toContain("Unknown"); @@ -116,8 +124,8 @@ describe("Slack Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const body = JSON.parse(callArgs[1].body); expect(body.attachments).toBeDefined(); expect(body.attachments[0].color).toBe(COLORS.green); @@ -139,8 +147,8 @@ describe("Slack Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const body = JSON.parse(callArgs[1].body); expect(body.attachments).toBeDefined(); expect(body.attachments[0].color).toBe(COLORS.yellow); @@ -152,8 +160,8 @@ describe("Slack Notifications", () => { await sendTestSlackMessage(webhookUrl); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe(webhookUrl); const body = JSON.parse(callArgs[1].body); @@ -163,20 +171,20 @@ describe("Slack Notifications", () => { }); test("Send Test Slack Message throws error on empty webhookUrl", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); - expect(sendTestSlackMessage("")).rejects.toThrow(); - assertSpyCalls(fetchMock, 0); + await expect(sendTestSlackMessage("")).rejects.toThrow(); + expect(fetchMock).toHaveBeenCalledTimes(0); }); test("Handle fetch error gracefully", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); const monitor = createMockMonitor(); const notification = selectNotificationSchema.parse( @@ -194,7 +202,7 @@ describe("Slack Notifications", () => { }), ).rejects.toThrow(); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/notifications/slack/tsconfig.json b/packages/notifications/slack/tsconfig.json index 76722cdc6..48baa5cfe 100644 --- a/packages/notifications/slack/tsconfig.json +++ b/packages/notifications/slack/tsconfig.json @@ -3,14 +3,5 @@ "include": ["src", "*.ts"], "compilerOptions": { "resolveJsonModule": true - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/notifications/slack/vitest.config.ts b/packages/notifications/slack/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/notifications/slack/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/notifications/telegram/package.json b/packages/notifications/telegram/package.json index 5d1d723cf..9c8de2b94 100644 --- a/packages/notifications/telegram/package.json +++ b/packages/notifications/telegram/package.json @@ -4,7 +4,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", + "test": "vitest run", "tsc": "tsc" }, "dependencies": { @@ -13,10 +13,10 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/notifications/telegram/src/index.test.ts b/packages/notifications/telegram/src/index.test.ts index 635b0c89c..8a8ee4469 100644 --- a/packages/notifications/telegram/src/index.test.ts +++ b/packages/notifications/telegram/src/index.test.ts @@ -1,7 +1,13 @@ import { selectNotificationSchema } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, type Stub, stub } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { sendAlert, @@ -12,18 +18,20 @@ import { } from "./index"; describe("Telegram Notifications", () => { - let fetchMock: Stub; + let fetchMock: MockInstance; const originalEnv = process.env.TELEGRAM_BOT_TOKEN; beforeEach(() => { process.env.TELEGRAM_BOT_TOKEN = "test-bot-token-123"; - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 200 })), - ); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 200 })), + ); }); afterEach(() => { - fetchMock.restore(); + fetchMock.mockRestore(); if (originalEnv) { process.env.TELEGRAM_BOT_TOKEN = originalEnv; } else { @@ -38,8 +46,8 @@ describe("Telegram Notifications", () => { await sendMessage({ chatId: "-123456789", message }); - assertSpyCalls(fetchMock, 1); - const url = new URL(new Request(fetchMock.calls[0].args[0]).url); + expect(fetchMock).toHaveBeenCalledTimes(1); + const url = new URL(new Request(fetchMock.mock.calls[0][0]).url); expect([...url.searchParams]).toEqual([ ["chat_id", "-123456789"], ["text", message], @@ -88,8 +96,8 @@ describe("Telegram Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toContain( "https://api.telegram.org/bottest-bot-token-123/sendMessage", ); @@ -113,8 +121,8 @@ describe("Telegram Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const text = new URL(new Request(callArgs[0]).url).searchParams.get("text"); expect(text).toContain("Connection timeout"); }); @@ -134,8 +142,8 @@ describe("Telegram Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const text = new URL(new Request(callArgs[0]).url).searchParams.get("text"); expect(text).toContain(monitor.name); expect(text).toContain(monitor.url); @@ -156,8 +164,8 @@ describe("Telegram Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const text = new URL(new Request(callArgs[0]).url).searchParams.get("text"); expect(text).toContain(monitor.name); expect(text).toContain(monitor.url); @@ -169,8 +177,8 @@ describe("Telegram Notifications", () => { }); expect(result).toBe(true); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toContain( "https://api.telegram.org/bottest-bot-token-123/sendMessage", ); @@ -178,24 +186,24 @@ describe("Telegram Notifications", () => { }); test("Send Test returns false on error", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); const result = await sendTest({ chatId: "123456789", }); expect(result).toBe(false); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); test("Handle fetch error gracefully", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); const monitor = createMockMonitor(); const notification = selectNotificationSchema.parse( @@ -213,7 +221,7 @@ describe("Telegram Notifications", () => { }), ).rejects.toThrow(); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); test("fetch not called when TELEGRAM_BOT_TOKEN is not set", async () => { @@ -235,6 +243,6 @@ describe("Telegram Notifications", () => { }), ).rejects.toThrow(); // Should not call fetch when token is missing - assertSpyCalls(fetchMock, 0); + expect(fetchMock).toHaveBeenCalledTimes(0); }); }); diff --git a/packages/notifications/telegram/tsconfig.json b/packages/notifications/telegram/tsconfig.json index 76722cdc6..48baa5cfe 100644 --- a/packages/notifications/telegram/tsconfig.json +++ b/packages/notifications/telegram/tsconfig.json @@ -3,14 +3,5 @@ "include": ["src", "*.ts"], "compilerOptions": { "resolveJsonModule": true - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/notifications/telegram/vitest.config.ts b/packages/notifications/telegram/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/notifications/telegram/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/notifications/twillio-sms/package.json b/packages/notifications/twillio-sms/package.json index bac3f742e..554966eff 100644 --- a/packages/notifications/twillio-sms/package.json +++ b/packages/notifications/twillio-sms/package.json @@ -4,7 +4,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", + "test": "vitest run", "tsc": "tsc" }, "dependencies": { @@ -15,11 +15,11 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", "@types/validator": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/notifications/twillio-sms/src/index.test.ts b/packages/notifications/twillio-sms/src/index.test.ts index 21b334ca8..1a012c247 100644 --- a/packages/notifications/twillio-sms/src/index.test.ts +++ b/packages/notifications/twillio-sms/src/index.test.ts @@ -1,12 +1,18 @@ import { selectNotificationSchema } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, stub, type Stub } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { sendAlert, sendDegraded, sendRecovery } from "./index"; describe("Twilio SMS Notifications", () => { - let fetchMock: Stub; + let fetchMock: MockInstance; const originalEnv = { TWILLIO_ACCOUNT_ID: process.env.TWILLIO_ACCOUNT_ID, TWILLIO_AUTH_TOKEN: process.env.TWILLIO_AUTH_TOKEN, @@ -15,13 +21,15 @@ describe("Twilio SMS Notifications", () => { beforeEach(() => { process.env.TWILLIO_ACCOUNT_ID = "test-account-id"; process.env.TWILLIO_AUTH_TOKEN = "test-auth-token"; - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 200 })), - ); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 200 })), + ); }); afterEach(() => { - fetchMock.restore(); + fetchMock.mockRestore(); if (originalEnv.TWILLIO_ACCOUNT_ID) { process.env.TWILLIO_ACCOUNT_ID = originalEnv.TWILLIO_ACCOUNT_ID; } else { @@ -71,8 +79,8 @@ describe("Twilio SMS Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; expect(callArgs[0]).toBe( "https://api.twilio.com/2010-04-01/Accounts/test-account-id/Messages.json", ); @@ -102,8 +110,8 @@ describe("Twilio SMS Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const formData = callArgs[1].body as FormData; expect(formData.get("Body")).toContain("error: Connection timeout"); }); @@ -123,8 +131,8 @@ describe("Twilio SMS Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const formData = callArgs[1].body as FormData; expect(formData.get("Body")).toContain("is up again"); expect(formData.get("Body")).toContain("API Health Check"); @@ -145,25 +153,25 @@ describe("Twilio SMS Notifications", () => { cronTimestamp: Date.now(), }); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; const formData = callArgs[1].body as FormData; expect(formData.get("Body")).toContain("is degraded"); expect(formData.get("Body")).toContain("API Health Check"); }); test("Handle fetch error gracefully", async () => { - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.reject(new Error("Network error")), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(new Error("Network error"))); const monitor = createMockMonitor(); const notification = selectNotificationSchema.parse( createMockNotification(), ); - expect( + await expect( sendAlert({ // @ts-expect-error monitor, @@ -174,6 +182,6 @@ describe("Twilio SMS Notifications", () => { }), ).rejects.toThrow(); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/notifications/twillio-sms/tsconfig.json b/packages/notifications/twillio-sms/tsconfig.json index 76722cdc6..48baa5cfe 100644 --- a/packages/notifications/twillio-sms/tsconfig.json +++ b/packages/notifications/twillio-sms/tsconfig.json @@ -3,14 +3,5 @@ "include": ["src", "*.ts"], "compilerOptions": { "resolveJsonModule": true - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/notifications/twillio-sms/vitest.config.ts b/packages/notifications/twillio-sms/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/notifications/twillio-sms/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/notifications/webhook/package.json b/packages/notifications/webhook/package.json index a2253a0cb..0eb54db1b 100644 --- a/packages/notifications/webhook/package.json +++ b/packages/notifications/webhook/package.json @@ -4,7 +4,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", + "test": "vitest run", "tsc": "tsc" }, "dependencies": { @@ -14,10 +14,10 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/notifications/webhook/src/index.test.ts b/packages/notifications/webhook/src/index.test.ts index f0000ee15..5c81d8ffa 100644 --- a/packages/notifications/webhook/src/index.test.ts +++ b/packages/notifications/webhook/src/index.test.ts @@ -1,35 +1,45 @@ -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, stub, type Stub } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { sendTest } from "./index"; describe("Webhook sendTest", () => { - let fetchMock: Stub; + let fetchMock: MockInstance; beforeEach(() => { - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 200 })), - ); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 200 })), + ); }); afterEach(() => { - fetchMock.restore(); + fetchMock.mockRestore(); }); test("should send test webhook successfully", async () => { const url = "https://example.com/webhook"; - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 200 })), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 200 })), + ); const result = await sendTest({ url }); expect(result).toBe(true); - assertSpyCalls(fetchMock, 1); - expect(fetchMock.calls[0].args[0]).toBe(url); - expect(fetchMock.calls[0].args[1]).toEqual( + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock.mock.calls[0][0]).toBe(url); + expect(fetchMock.mock.calls[0][1]).toEqual( expect.objectContaining({ method: "post", body: expect.any(String), @@ -37,7 +47,7 @@ describe("Webhook sendTest", () => { }), ); - const callArgs = fetchMock.calls[0].args; + const callArgs = fetchMock.mock.calls[0]; const body = JSON.parse(callArgs[1].body); expect(body).toMatchObject({ monitor: { @@ -58,17 +68,19 @@ describe("Webhook sendTest", () => { { key: "Authorization", value: "Bearer token123" }, { key: "Content-Type", value: "application/json" }, ]; - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 200 })), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 200 })), + ); const result = await sendTest({ url, headers }); expect(result).toBe(true); - assertSpyCalls(fetchMock, 1); - expect(fetchMock.calls[0].args[0]).toBe(url); - expect(fetchMock.calls[0].args[1]).toEqual( + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock.mock.calls[0][0]).toBe(url); + expect(fetchMock.mock.calls[0][1]).toEqual( expect.objectContaining({ method: "post", body: expect.any(String), @@ -82,63 +94,73 @@ describe("Webhook sendTest", () => { test("should throw error when response is not ok", async () => { const url = "https://example.com/webhook"; - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 400 })), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 400 })), + ); await expect(sendTest({ url })).rejects.toThrow("Failed to send test"); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); test("should throw error when fetch fails", async () => { const url = "https://example.com/webhook"; const networkError = new Error("Network error"); - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => Promise.reject(networkError)); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => Promise.reject(networkError)); await expect(sendTest({ url })).rejects.toThrow("Failed to send test"); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); test("should send test webhook with empty headers array", async () => { const url = "https://example.com/webhook"; const headers: { key: string; value: string }[] = []; - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 200 })), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 200 })), + ); const result = await sendTest({ url, headers }); expect(result).toBe(true); - assertSpyCalls(fetchMock, 1); - const callArgs = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const callArgs = fetchMock.mock.calls[0]; // Empty headers array should still include Content-Type expect(callArgs[1].headers).toEqual({ "Content-Type": "application/json" }); }); test("should send test webhook with 500 status code", async () => { const url = "https://example.com/webhook"; - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 500 })), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 500 })), + ); await expect(sendTest({ url })).rejects.toThrow("Failed to send test"); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); test("should send test webhook with 201 status code (success)", async () => { const url = "https://example.com/webhook"; - fetchMock.restore(); - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 201 })), - ); + fetchMock.mockRestore(); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 201 })), + ); const result = await sendTest({ url }); expect(result).toBe(true); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/notifications/webhook/tsconfig.json b/packages/notifications/webhook/tsconfig.json index c240dd4b7..fccf088fc 100644 --- a/packages/notifications/webhook/tsconfig.json +++ b/packages/notifications/webhook/tsconfig.json @@ -1,13 +1,5 @@ { "extends": "@openstatus/tsconfig/base.json", "include": ["src", "*.ts"], - "exclude": [ - "**/*.test.ts", - "**/*.spec.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + "exclude": ["**/*.test.ts", "**/*.spec.ts"] } diff --git a/packages/notifications/webhook/vitest.config.ts b/packages/notifications/webhook/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/notifications/webhook/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/proto/tsconfig.json b/packages/proto/tsconfig.json index b6dc2c0ec..14e8ce4c8 100644 --- a/packages/proto/tsconfig.json +++ b/packages/proto/tsconfig.json @@ -14,13 +14,5 @@ "allowImportingTsExtensions": true }, "include": ["gen/ts/**/*.ts"], - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + "exclude": ["node_modules"] } diff --git a/packages/regions/tsconfig.json b/packages/regions/tsconfig.json index 9f7ee457b..aed15c74a 100644 --- a/packages/regions/tsconfig.json +++ b/packages/regions/tsconfig.json @@ -1,13 +1,4 @@ { "extends": "@openstatus/tsconfig/base.json", - "include": ["src", "*.ts"], - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + "include": ["src", "*.ts"] } diff --git a/packages/services/package.json b/packages/services/package.json index 0f031d853..83490dcd9 100644 --- a/packages/services/package.json +++ b/packages/services/package.json @@ -160,7 +160,7 @@ }, "scripts": { "check": "tsc --noEmit", - "test": "NODE_ENV=test RESEND_API_KEY=test-key deno test --parallel -A --no-check --sloppy-imports", + "test": "NODE_ENV=test RESEND_API_KEY=test-key vitest run", "tsc": "tsc --noEmit" }, "dependencies": { @@ -179,9 +179,9 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/services/src/__tests__/attribution.test.ts b/packages/services/src/__tests__/attribution.test.ts index 4a9d4206c..06ed549a5 100644 --- a/packages/services/src/__tests__/attribution.test.ts +++ b/packages/services/src/__tests__/attribution.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { toAgentUser, toAttributedUserDetail } from "../attribution"; diff --git a/packages/services/src/__tests__/context.test.ts b/packages/services/src/__tests__/context.test.ts index c285add6a..2af0b6b6e 100644 --- a/packages/services/src/__tests__/context.test.ts +++ b/packages/services/src/__tests__/context.test.ts @@ -1,7 +1,6 @@ import { db, eq } from "@openstatus/db"; import { monitor, page } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/__tests__/features.test.ts b/packages/services/src/__tests__/features.test.ts index 334971a8c..6b083b890 100644 --- a/packages/services/src/__tests__/features.test.ts +++ b/packages/services/src/__tests__/features.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { ForbiddenError } from "../errors"; import { type Feature, isFeatureEnabled, requireFeature } from "../features"; diff --git a/packages/services/src/__tests__/limits.test.ts b/packages/services/src/__tests__/limits.test.ts index b8c84952d..c3d500bb7 100644 --- a/packages/services/src/__tests__/limits.test.ts +++ b/packages/services/src/__tests__/limits.test.ts @@ -7,8 +7,7 @@ import { pageComponent, workspace, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/__tests__/retry.test.ts b/packages/services/src/__tests__/retry.test.ts index ada57ea13..f014ac750 100644 --- a/packages/services/src/__tests__/retry.test.ts +++ b/packages/services/src/__tests__/retry.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { NotFoundError } from "../errors"; import { diff --git a/packages/services/src/__tests__/with-test-transaction.test.ts b/packages/services/src/__tests__/with-test-transaction.test.ts index cb9a1f643..f7b90799b 100644 --- a/packages/services/src/__tests__/with-test-transaction.test.ts +++ b/packages/services/src/__tests__/with-test-transaction.test.ts @@ -1,7 +1,6 @@ import { db, eq } from "@openstatus/db"; import { auditLog } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterAll, describe, test } from "@std/testing/bdd"; +import { afterAll, describe, expect, test } from "vitest"; import { SEEDED_WORKSPACE_TEAM_ID } from "../../test/fixtures"; import { withTestTransaction } from "../../test/helpers"; diff --git a/packages/services/src/__tests__/with-transaction.test.ts b/packages/services/src/__tests__/with-transaction.test.ts index cf6a9d3d9..bb32929f3 100644 --- a/packages/services/src/__tests__/with-transaction.test.ts +++ b/packages/services/src/__tests__/with-transaction.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture } from "../../test/helpers"; import type { DB, ServiceContext } from "../context"; diff --git a/packages/services/src/agent-tools/__tests__/content.test.ts b/packages/services/src/agent-tools/__tests__/content.test.ts index c2704eccb..cf9d390bc 100644 --- a/packages/services/src/agent-tools/__tests__/content.test.ts +++ b/packages/services/src/agent-tools/__tests__/content.test.ts @@ -1,6 +1,4 @@ -import { expect } from "@std/expect"; -import { afterEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, spy } from "@std/testing/mock"; +import { afterEach, describe, expect, test, vi } from "vitest"; import type { ServiceContext } from "../../context"; import { getContentPageTool, searchContentTool } from "../content"; @@ -15,7 +13,7 @@ afterEach(() => { }); function mockFetch(response: Response) { - const fn = spy(async () => response); + const fn = vi.fn(async () => response); globalThis.fetch = fn as unknown as typeof fetch; return fn; } @@ -43,8 +41,8 @@ describe("search_content", () => { ctx, input: searchContentTool.inputSchema.parse({ query: "pricing" }), }); - assertSpyCalls(fn, 1); - const url = String((fn.calls[0] as { args: unknown[] }).args[0]); + expect(fn).toHaveBeenCalledTimes(1); + const url = String((fn.mock.calls[0] as unknown[] | undefined)?.[0]); expect(url).toContain("/api/search?p=all&q=pricing"); }); @@ -54,7 +52,7 @@ describe("search_content", () => { ctx, input: { query: "pricing", type: "compare" }, }); - const url = String((fn.calls[0] as { args: unknown[] }).args[0]); + const url = String((fn.mock.calls[0] as unknown[] | undefined)?.[0]); expect(url).toContain("/api/search?p=compare&q=pricing"); }); @@ -140,7 +138,7 @@ describe("get_content_page", () => { expect(result.markdown).toBe("# Pricing\n\nPlans."); expect(result.truncated).toBe(false); expect(result.url).toBe("https://www.openstatus.dev/pricing"); - const url = String((fn.calls[0] as { args: unknown[] }).args[0]); + const url = String((fn.mock.calls[0] as unknown[] | undefined)?.[0]); expect(url).toBe("https://www.openstatus.dev/api/markdown/pricing"); }); @@ -198,6 +196,6 @@ describe("get_content_page", () => { true, ); } - assertSpyCalls(fn, 0); + expect(fn).toHaveBeenCalledTimes(0); }); }); diff --git a/packages/services/src/agent-tools/__tests__/docs.test.ts b/packages/services/src/agent-tools/__tests__/docs.test.ts index 1537e16d5..70cd4d90d 100644 --- a/packages/services/src/agent-tools/__tests__/docs.test.ts +++ b/packages/services/src/agent-tools/__tests__/docs.test.ts @@ -1,6 +1,4 @@ -import { expect } from "@std/expect"; -import { afterEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, spy } from "@std/testing/mock"; +import { afterEach, describe, expect, test, vi } from "vitest"; import type { ServiceContext } from "../../context"; import { getDocPageTool, searchDocsTool } from "../docs"; @@ -15,7 +13,7 @@ afterEach(() => { }); function mockFetch(response: Response) { - const fn = spy(async () => response); + const fn = vi.fn(async () => response); globalThis.fetch = fn as unknown as typeof fetch; return fn; } @@ -163,6 +161,6 @@ describe("get_doc_page", () => { ); expect(getDocPageTool.outputSchema.safeParse(result).success).toBe(true); } - assertSpyCalls(fn, 0); + expect(fn).toHaveBeenCalledTimes(0); }); }); diff --git a/packages/services/src/agent-tools/__tests__/incident.test.ts b/packages/services/src/agent-tools/__tests__/incident.test.ts index 2e0506b6c..eb952f3ac 100644 --- a/packages/services/src/agent-tools/__tests__/incident.test.ts +++ b/packages/services/src/agent-tools/__tests__/incident.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/agent-tools/__tests__/maintenance-updates.test.ts b/packages/services/src/agent-tools/__tests__/maintenance-updates.test.ts index 7687bdecf..35865b96d 100644 --- a/packages/services/src/agent-tools/__tests__/maintenance-updates.test.ts +++ b/packages/services/src/agent-tools/__tests__/maintenance-updates.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { agentTools, buildAgentSystemPrompt } from "../index"; diff --git a/packages/services/src/agent-tools/__tests__/prompt.test.ts b/packages/services/src/agent-tools/__tests__/prompt.test.ts index 23950fd94..440c95b38 100644 --- a/packages/services/src/agent-tools/__tests__/prompt.test.ts +++ b/packages/services/src/agent-tools/__tests__/prompt.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { type AgentSystemPromptOptions, diff --git a/packages/services/src/agent-tools/__tests__/shape-equivalence.test.ts b/packages/services/src/agent-tools/__tests__/shape-equivalence.test.ts index cde341d05..f87ee9324 100644 --- a/packages/services/src/agent-tools/__tests__/shape-equivalence.test.ts +++ b/packages/services/src/agent-tools/__tests__/shape-equivalence.test.ts @@ -1,6 +1,5 @@ import { createMonitor } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, makeUserCtx } from "../../../test/helpers"; import type { ServiceContext } from "../../context"; diff --git a/packages/services/src/agent-tools/__tests__/status-report-impacts.test.ts b/packages/services/src/agent-tools/__tests__/status-report-impacts.test.ts index 409636b77..d659b8836 100644 --- a/packages/services/src/agent-tools/__tests__/status-report-impacts.test.ts +++ b/packages/services/src/agent-tools/__tests__/status-report-impacts.test.ts @@ -1,6 +1,5 @@ import { page, pageComponent } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/agent-tools/__tests__/summary-refs.test.ts b/packages/services/src/agent-tools/__tests__/summary-refs.test.ts index 1c47d8222..0049d42a4 100644 --- a/packages/services/src/agent-tools/__tests__/summary-refs.test.ts +++ b/packages/services/src/agent-tools/__tests__/summary-refs.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { agentTools } from "../index"; import type { AnyAgentTool, SummaryLine } from "../types"; diff --git a/packages/services/src/api-key/__tests__/api-key.test.ts b/packages/services/src/api-key/__tests__/api-key.test.ts index 0c50123fa..eb04afcd3 100644 --- a/packages/services/src/api-key/__tests__/api-key.test.ts +++ b/packages/services/src/api-key/__tests__/api-key.test.ts @@ -1,7 +1,6 @@ import { eq } from "@openstatus/db"; import { apiKey } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/audit/__tests__/audit.test.ts b/packages/services/src/audit/__tests__/audit.test.ts index 6296e1096..4e622426d 100644 --- a/packages/services/src/audit/__tests__/audit.test.ts +++ b/packages/services/src/audit/__tests__/audit.test.ts @@ -1,8 +1,7 @@ import { db, eq } from "@openstatus/db"; import { auditLog, user } from "@openstatus/db/src/schema"; import type { AuditEntry } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { clearAuditLog, diff --git a/packages/services/src/auth/__tests__/matches-scope.test.ts b/packages/services/src/auth/__tests__/matches-scope.test.ts index 0a70f4750..41c7c03a6 100644 --- a/packages/services/src/auth/__tests__/matches-scope.test.ts +++ b/packages/services/src/auth/__tests__/matches-scope.test.ts @@ -1,6 +1,5 @@ import type { Scope } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { matchesScope } from "../matches-scope"; diff --git a/packages/services/src/auth/__tests__/require-role.test.ts b/packages/services/src/auth/__tests__/require-role.test.ts index a18621a5c..3dec2c1c9 100644 --- a/packages/services/src/auth/__tests__/require-role.test.ts +++ b/packages/services/src/auth/__tests__/require-role.test.ts @@ -3,8 +3,7 @@ import { addUserToWorkspace, createUser, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/auth/__tests__/require-scope.test.ts b/packages/services/src/auth/__tests__/require-scope.test.ts index b2c708d87..96962b628 100644 --- a/packages/services/src/auth/__tests__/require-scope.test.ts +++ b/packages/services/src/auth/__tests__/require-scope.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import type { Actor, ServiceContext } from "../../context"; import { ForbiddenError } from "../../errors"; diff --git a/packages/services/src/external-service-component/__tests__/external-service-component.test.ts b/packages/services/src/external-service-component/__tests__/external-service-component.test.ts index e8062d017..068462018 100644 --- a/packages/services/src/external-service-component/__tests__/external-service-component.test.ts +++ b/packages/services/src/external-service-component/__tests__/external-service-component.test.ts @@ -4,8 +4,7 @@ import { externalService, externalServiceComponent, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterEach, describe, test } from "@std/testing/bdd"; +import { afterEach, describe, expect, test } from "vitest"; import { getExternalComponentBySlug, diff --git a/packages/services/src/external-service-component/__tests__/internal.test.ts b/packages/services/src/external-service-component/__tests__/internal.test.ts index 7b304e958..9f2ef2c02 100644 --- a/packages/services/src/external-service-component/__tests__/internal.test.ts +++ b/packages/services/src/external-service-component/__tests__/internal.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { assignComponentSlugs, slugifyComponentName } from "../internal"; diff --git a/packages/services/src/external-service-incident/__tests__/external-service-incident.test.ts b/packages/services/src/external-service-incident/__tests__/external-service-incident.test.ts index 9f1ba0138..f2c8a01d7 100644 --- a/packages/services/src/external-service-incident/__tests__/external-service-incident.test.ts +++ b/packages/services/src/external-service-incident/__tests__/external-service-incident.test.ts @@ -3,8 +3,7 @@ import { externalService, externalServiceIncident, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterEach, describe, test } from "@std/testing/bdd"; +import { afterEach, describe, expect, test } from "vitest"; import { type UpsertExternalIncidentInput, diff --git a/packages/services/src/external-service-report/__tests__/record.test.ts b/packages/services/src/external-service-report/__tests__/record.test.ts index 853831f78..b9f31f928 100644 --- a/packages/services/src/external-service-report/__tests__/record.test.ts +++ b/packages/services/src/external-service-report/__tests__/record.test.ts @@ -4,8 +4,7 @@ import { externalServiceComponent, externalServiceReport, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterEach, describe, test } from "@std/testing/bdd"; +import { afterEach, describe, expect, test } from "vitest"; import { NotFoundError } from "../../errors"; import { diff --git a/packages/services/src/external-service/__tests__/external-service.test.ts b/packages/services/src/external-service/__tests__/external-service.test.ts index 6dc0c8977..be3791012 100644 --- a/packages/services/src/external-service/__tests__/external-service.test.ts +++ b/packages/services/src/external-service/__tests__/external-service.test.ts @@ -1,7 +1,6 @@ import { db, like, sql } from "@openstatus/db"; import { externalService } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterEach, describe, test } from "@std/testing/bdd"; +import { afterEach, describe, expect, test } from "vitest"; import { ConflictError } from "../../errors"; import { assertSlugAvailable } from "../internal"; diff --git a/packages/services/src/external-service/__tests__/update-provider.test.ts b/packages/services/src/external-service/__tests__/update-provider.test.ts index 5eb9cec57..957edabaa 100644 --- a/packages/services/src/external-service/__tests__/update-provider.test.ts +++ b/packages/services/src/external-service/__tests__/update-provider.test.ts @@ -1,7 +1,6 @@ import { db, eq, like } from "@openstatus/db"; import { externalService } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterEach, describe, test } from "@std/testing/bdd"; +import { afterEach, describe, expect, test } from "vitest"; import { applyDetectedProvider } from "../update-provider"; diff --git a/packages/services/src/frozen-uptime/__tests__/compute.test.ts b/packages/services/src/frozen-uptime/__tests__/compute.test.ts index c7464b513..10a1fc0ab 100644 --- a/packages/services/src/frozen-uptime/__tests__/compute.test.ts +++ b/packages/services/src/frozen-uptime/__tests__/compute.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { type ComputeCountRow, diff --git a/packages/services/src/frozen-uptime/__tests__/freeze.test.ts b/packages/services/src/frozen-uptime/__tests__/freeze.test.ts index 5f20f598f..fafc0877f 100644 --- a/packages/services/src/frozen-uptime/__tests__/freeze.test.ts +++ b/packages/services/src/frozen-uptime/__tests__/freeze.test.ts @@ -4,8 +4,7 @@ import { monitor, workspace, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/frozen-uptime/__tests__/get-history.test.ts b/packages/services/src/frozen-uptime/__tests__/get-history.test.ts index 2a86168cf..f214bfcc8 100644 --- a/packages/services/src/frozen-uptime/__tests__/get-history.test.ts +++ b/packages/services/src/frozen-uptime/__tests__/get-history.test.ts @@ -10,8 +10,7 @@ import { statusReportUpdateToPageComponents, } from "@openstatus/db/src/schema"; import type { FrozenMonitorUptimeDay } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/frozen-uptime/__tests__/run.test.ts b/packages/services/src/frozen-uptime/__tests__/run.test.ts index dcb7d4862..e31fccfa2 100644 --- a/packages/services/src/frozen-uptime/__tests__/run.test.ts +++ b/packages/services/src/frozen-uptime/__tests__/run.test.ts @@ -1,7 +1,6 @@ import { eq } from "@openstatus/db"; import { frozenMonitorUptime, monitor } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { SEEDED_WORKSPACE_TEAM_ID } from "../../../test/fixtures"; import { withTestTransaction } from "../../../test/helpers"; diff --git a/packages/services/src/frozen-uptime/__tests__/schemas.test.ts b/packages/services/src/frozen-uptime/__tests__/schemas.test.ts index 1250673de..5cc8f3438 100644 --- a/packages/services/src/frozen-uptime/__tests__/schemas.test.ts +++ b/packages/services/src/frozen-uptime/__tests__/schemas.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { FreezeMonitorMonthInput } from "../schemas"; diff --git a/packages/services/src/import/__tests__/attribution.test.ts b/packages/services/src/import/__tests__/attribution.test.ts index 6385cf854..61fa478b2 100644 --- a/packages/services/src/import/__tests__/attribution.test.ts +++ b/packages/services/src/import/__tests__/attribution.test.ts @@ -6,8 +6,7 @@ import { statusReportUpdate, } from "@openstatus/db/src/schema"; import type { PhaseResult } from "@openstatus/importers"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/import/__tests__/incidents-phase.test.ts b/packages/services/src/import/__tests__/incidents-phase.test.ts index 77e3514ff..94c186a88 100644 --- a/packages/services/src/import/__tests__/incidents-phase.test.ts +++ b/packages/services/src/import/__tests__/incidents-phase.test.ts @@ -8,8 +8,7 @@ import { statusReportsToPageComponents, } from "@openstatus/db/src/schema"; import type { PhaseResult } from "@openstatus/importers"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/import/__tests__/unit.test.ts b/packages/services/src/import/__tests__/unit.test.ts index ad3b74cfa..aa2addd95 100644 --- a/packages/services/src/import/__tests__/unit.test.ts +++ b/packages/services/src/import/__tests__/unit.test.ts @@ -3,8 +3,7 @@ import { monitor, pageComponent } from "@openstatus/db/src/schema"; import { allPlans } from "@openstatus/db/src/schema/plan/config"; import type { Limits } from "@openstatus/db/src/schema/plan/schema"; import type { ImportSummary } from "@openstatus/importers"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { addLimitWarnings, clampPeriodicity, computePhaseStatus } from "../"; import { withTestTransaction } from "../../../test/helpers"; diff --git a/packages/services/src/incident/__tests__/hooks.test.ts b/packages/services/src/incident/__tests__/hooks.test.ts index 0f149c6d5..ceaf298df 100644 --- a/packages/services/src/incident/__tests__/hooks.test.ts +++ b/packages/services/src/incident/__tests__/hooks.test.ts @@ -6,8 +6,7 @@ import { createSlackUser, createUser, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/incident/__tests__/incident.test.ts b/packages/services/src/incident/__tests__/incident.test.ts index 6b2384d7f..8ad6f4f18 100644 --- a/packages/services/src/incident/__tests__/incident.test.ts +++ b/packages/services/src/incident/__tests__/incident.test.ts @@ -14,8 +14,7 @@ import { createMonitor, createUser, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { clearAuditLogFor, diff --git a/packages/services/src/incident/__tests__/postmortem-draft.test.ts b/packages/services/src/incident/__tests__/postmortem-draft.test.ts index 98977c4fe..7e8cdf77d 100644 --- a/packages/services/src/incident/__tests__/postmortem-draft.test.ts +++ b/packages/services/src/incident/__tests__/postmortem-draft.test.ts @@ -1,6 +1,5 @@ import { integration } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/incident/__tests__/postmortem.test.ts b/packages/services/src/incident/__tests__/postmortem.test.ts index c1a749daf..ea4a0fffb 100644 --- a/packages/services/src/incident/__tests__/postmortem.test.ts +++ b/packages/services/src/incident/__tests__/postmortem.test.ts @@ -4,8 +4,7 @@ import { addUserToWorkspace, createUser, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/incident/__tests__/reminders.test.ts b/packages/services/src/incident/__tests__/reminders.test.ts index cf89978b5..f0a711861 100644 --- a/packages/services/src/incident/__tests__/reminders.test.ts +++ b/packages/services/src/incident/__tests__/reminders.test.ts @@ -4,8 +4,7 @@ import { createIncidentEvent, createSlackUser, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/incident/__tests__/slack-flow.test.ts b/packages/services/src/incident/__tests__/slack-flow.test.ts index 2ab5f30b5..976bcb012 100644 --- a/packages/services/src/incident/__tests__/slack-flow.test.ts +++ b/packages/services/src/incident/__tests__/slack-flow.test.ts @@ -4,8 +4,7 @@ import { addUserToWorkspace, createUser, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/integration/__tests__/install-slack-agent.test.ts b/packages/services/src/integration/__tests__/install-slack-agent.test.ts index fd9e3e9a9..c136ea825 100644 --- a/packages/services/src/integration/__tests__/install-slack-agent.test.ts +++ b/packages/services/src/integration/__tests__/install-slack-agent.test.ts @@ -1,7 +1,6 @@ import { and, desc, eq } from "@openstatus/db"; import { integration } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { clearAuditLog, diff --git a/packages/services/src/integration/__tests__/slack-scopes.test.ts b/packages/services/src/integration/__tests__/slack-scopes.test.ts index df658546d..f7ad66a56 100644 --- a/packages/services/src/integration/__tests__/slack-scopes.test.ts +++ b/packages/services/src/integration/__tests__/slack-scopes.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { SLACK_BOT_SCOPES, missingSlackScopes } from "../slack-scopes"; diff --git a/packages/services/src/integration/__tests__/uninstall-slack-agent.test.ts b/packages/services/src/integration/__tests__/uninstall-slack-agent.test.ts index 8ee389377..24e0cc394 100644 --- a/packages/services/src/integration/__tests__/uninstall-slack-agent.test.ts +++ b/packages/services/src/integration/__tests__/uninstall-slack-agent.test.ts @@ -10,8 +10,7 @@ import { createPage, createSlackUser, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/invitation/__tests__/invitation.test.ts b/packages/services/src/invitation/__tests__/invitation.test.ts index 02127565a..272b20424 100644 --- a/packages/services/src/invitation/__tests__/invitation.test.ts +++ b/packages/services/src/invitation/__tests__/invitation.test.ts @@ -1,8 +1,7 @@ import { eq } from "@openstatus/db"; import { invitation, usersToWorkspaces } from "@openstatus/db/src/schema"; import { createUser } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/maintenance/__tests__/attribution.test.ts b/packages/services/src/maintenance/__tests__/attribution.test.ts index bb46958ea..41f46e650 100644 --- a/packages/services/src/maintenance/__tests__/attribution.test.ts +++ b/packages/services/src/maintenance/__tests__/attribution.test.ts @@ -1,8 +1,7 @@ import { db, eq } from "@openstatus/db"; import { maintenance, page } from "@openstatus/db/src/schema"; import { createUser } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/maintenance/__tests__/maintenance.test.ts b/packages/services/src/maintenance/__tests__/maintenance.test.ts index 98cdcb359..cf4bc1398 100644 --- a/packages/services/src/maintenance/__tests__/maintenance.test.ts +++ b/packages/services/src/maintenance/__tests__/maintenance.test.ts @@ -5,14 +5,14 @@ import { page, pageComponent, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; import { afterAll, beforeAll, beforeEach, describe, + expect, test, -} from "@std/testing/bdd"; +} from "vitest"; import { expectAuditRow, diff --git a/packages/services/src/member/__tests__/display-name.test.ts b/packages/services/src/member/__tests__/display-name.test.ts index 8cc7adc83..595b11b50 100644 --- a/packages/services/src/member/__tests__/display-name.test.ts +++ b/packages/services/src/member/__tests__/display-name.test.ts @@ -2,8 +2,7 @@ import { addUserToWorkspace, createUser, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, makeSystemCtx } from "../../../test/helpers"; import type { ServiceContext } from "../../context"; diff --git a/packages/services/src/member/__tests__/find-by-email.test.ts b/packages/services/src/member/__tests__/find-by-email.test.ts index f08197e96..8aa569a65 100644 --- a/packages/services/src/member/__tests__/find-by-email.test.ts +++ b/packages/services/src/member/__tests__/find-by-email.test.ts @@ -4,8 +4,7 @@ import { addUserToWorkspace, createUser, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/member/__tests__/member.test.ts b/packages/services/src/member/__tests__/member.test.ts index ce1c1953f..179dec3b8 100644 --- a/packages/services/src/member/__tests__/member.test.ts +++ b/packages/services/src/member/__tests__/member.test.ts @@ -4,8 +4,7 @@ import { addUserToWorkspace, createUser, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/monitor-incident/__tests__/monitor-incident.test.ts b/packages/services/src/monitor-incident/__tests__/monitor-incident.test.ts index cfcb02e09..a5cbf141f 100644 --- a/packages/services/src/monitor-incident/__tests__/monitor-incident.test.ts +++ b/packages/services/src/monitor-incident/__tests__/monitor-incident.test.ts @@ -1,7 +1,6 @@ import { db, eq } from "@openstatus/db"; import { monitorIncidentTable, monitor } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { expectAuditRow, diff --git a/packages/services/src/monitor-tag/__tests__/monitor-tag.test.ts b/packages/services/src/monitor-tag/__tests__/monitor-tag.test.ts index 1f8e4712a..4f983ff76 100644 --- a/packages/services/src/monitor-tag/__tests__/monitor-tag.test.ts +++ b/packages/services/src/monitor-tag/__tests__/monitor-tag.test.ts @@ -1,7 +1,6 @@ import { eq } from "@openstatus/db"; import { monitorTag, workspace } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { clearAuditLog, diff --git a/packages/services/src/monitor/__tests__/monitor.test.ts b/packages/services/src/monitor/__tests__/monitor.test.ts index 25fc75c6b..32772046b 100644 --- a/packages/services/src/monitor/__tests__/monitor.test.ts +++ b/packages/services/src/monitor/__tests__/monitor.test.ts @@ -10,8 +10,7 @@ import { workspace, } from "@openstatus/db/src/schema"; import { createWorkspace } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { clearAuditLog, diff --git a/packages/services/src/monitor/__tests__/reads.test.ts b/packages/services/src/monitor/__tests__/reads.test.ts index 61416d7dd..4fea52569 100644 --- a/packages/services/src/monitor/__tests__/reads.test.ts +++ b/packages/services/src/monitor/__tests__/reads.test.ts @@ -5,8 +5,7 @@ import { privateLocation, privateLocationToMonitors, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/monitor/__tests__/response-logs-cursor.test.ts b/packages/services/src/monitor/__tests__/response-logs-cursor.test.ts index 3432605b0..7854c3b02 100644 --- a/packages/services/src/monitor/__tests__/response-logs-cursor.test.ts +++ b/packages/services/src/monitor/__tests__/response-logs-cursor.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { selectWindow, diff --git a/packages/services/src/monitor/__tests__/response-logs-internal.test.ts b/packages/services/src/monitor/__tests__/response-logs-internal.test.ts index d483f1bdf..978674f0e 100644 --- a/packages/services/src/monitor/__tests__/response-logs-internal.test.ts +++ b/packages/services/src/monitor/__tests__/response-logs-internal.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { redactSensitiveBody } from "../response-logs-internal"; diff --git a/packages/services/src/monitor/__tests__/stream-monitor-preview.test.ts b/packages/services/src/monitor/__tests__/stream-monitor-preview.test.ts index 7e777e13e..dcb211dca 100644 --- a/packages/services/src/monitor/__tests__/stream-monitor-preview.test.ts +++ b/packages/services/src/monitor/__tests__/stream-monitor-preview.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/monitor/__tests__/url-safety.test.ts b/packages/services/src/monitor/__tests__/url-safety.test.ts index 4d7256af1..414cbd098 100644 --- a/packages/services/src/monitor/__tests__/url-safety.test.ts +++ b/packages/services/src/monitor/__tests__/url-safety.test.ts @@ -1,7 +1,6 @@ import { selectWorkspaceSchema } from "@openstatus/db/src/schema"; import { createWorkspace } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { makeUserCtx, withTestTransaction } from "../../../test/helpers"; import type { ServiceContext } from "../../context"; diff --git a/packages/services/src/notification/__tests__/notification.test.ts b/packages/services/src/notification/__tests__/notification.test.ts index b66915efd..c9f864ee3 100644 --- a/packages/services/src/notification/__tests__/notification.test.ts +++ b/packages/services/src/notification/__tests__/notification.test.ts @@ -7,8 +7,7 @@ import { workspace, } from "@openstatus/db/src/schema"; import { getLimits } from "@openstatus/db/src/schema/plan/utils"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { expectAuditRow, diff --git a/packages/services/src/oauth/__tests__/cimd.test.ts b/packages/services/src/oauth/__tests__/cimd.test.ts index dd9960411..bbbc4666b 100644 --- a/packages/services/src/oauth/__tests__/cimd.test.ts +++ b/packages/services/src/oauth/__tests__/cimd.test.ts @@ -1,7 +1,6 @@ import { eq } from "@openstatus/db"; import { oauthClient } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/oauth/__tests__/crypto.test.ts b/packages/services/src/oauth/__tests__/crypto.test.ts index 9bc0f3714..a1b753b76 100644 --- a/packages/services/src/oauth/__tests__/crypto.test.ts +++ b/packages/services/src/oauth/__tests__/crypto.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { constantTimeEqual, diff --git a/packages/services/src/oauth/__tests__/metadata.test.ts b/packages/services/src/oauth/__tests__/metadata.test.ts index b7f950b33..3a7e8e78a 100644 --- a/packages/services/src/oauth/__tests__/metadata.test.ts +++ b/packages/services/src/oauth/__tests__/metadata.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { authorizationServerMetadata, diff --git a/packages/services/src/oauth/__tests__/oauth.test.ts b/packages/services/src/oauth/__tests__/oauth.test.ts index cdc14e378..60b657d5d 100644 --- a/packages/services/src/oauth/__tests__/oauth.test.ts +++ b/packages/services/src/oauth/__tests__/oauth.test.ts @@ -11,8 +11,7 @@ import { addUserToWorkspace, createUser, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/oauth/__tests__/redirect-allowlist.test.ts b/packages/services/src/oauth/__tests__/redirect-allowlist.test.ts index 07b79b43d..c80a404c1 100644 --- a/packages/services/src/oauth/__tests__/redirect-allowlist.test.ts +++ b/packages/services/src/oauth/__tests__/redirect-allowlist.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { isAllowedRedirectUri, diff --git a/packages/services/src/oauth/__tests__/schemas.test.ts b/packages/services/src/oauth/__tests__/schemas.test.ts index e1e5971fd..2927ee542 100644 --- a/packages/services/src/oauth/__tests__/schemas.test.ts +++ b/packages/services/src/oauth/__tests__/schemas.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { RegisterClientInput, diff --git a/packages/services/src/page-access/access-predicates.test.ts b/packages/services/src/page-access/access-predicates.test.ts index e5d7bb223..5f1ef8921 100644 --- a/packages/services/src/page-access/access-predicates.test.ts +++ b/packages/services/src/page-access/access-predicates.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { isEmailDomainAuthorized, diff --git a/packages/services/src/page-access/client-ip.test.ts b/packages/services/src/page-access/client-ip.test.ts index 45b2f7c42..0d1f7a858 100644 --- a/packages/services/src/page-access/client-ip.test.ts +++ b/packages/services/src/page-access/client-ip.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { resolveClientIp } from "./client-ip"; diff --git a/packages/services/src/page-access/evaluate-markdown-gate.test.ts b/packages/services/src/page-access/evaluate-markdown-gate.test.ts index 2386573a4..4a76ea45a 100644 --- a/packages/services/src/page-access/evaluate-markdown-gate.test.ts +++ b/packages/services/src/page-access/evaluate-markdown-gate.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { evaluateMarkdownGate } from "./evaluate-markdown-gate"; diff --git a/packages/services/src/page-access/is-ip-allowed.test.ts b/packages/services/src/page-access/is-ip-allowed.test.ts index c963ca317..8aecc42a5 100644 --- a/packages/services/src/page-access/is-ip-allowed.test.ts +++ b/packages/services/src/page-access/is-ip-allowed.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { isIpAllowed } from "./is-ip-allowed"; diff --git a/packages/services/src/page-component-group/__tests__/page-component-group.test.ts b/packages/services/src/page-component-group/__tests__/page-component-group.test.ts index 0eb1e637b..a6b51cbee 100644 --- a/packages/services/src/page-component-group/__tests__/page-component-group.test.ts +++ b/packages/services/src/page-component-group/__tests__/page-component-group.test.ts @@ -1,7 +1,6 @@ import { db, eq } from "@openstatus/db"; import { page, pageComponentGroup } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/page-component/__tests__/get-daily-summary.test.ts b/packages/services/src/page-component/__tests__/get-daily-summary.test.ts index 9f10c94a0..342420d33 100644 --- a/packages/services/src/page-component/__tests__/get-daily-summary.test.ts +++ b/packages/services/src/page-component/__tests__/get-daily-summary.test.ts @@ -9,8 +9,7 @@ import { statusReportUpdateToPageComponents, statusReportsToPageComponents, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/page-component/__tests__/page-component.test.ts b/packages/services/src/page-component/__tests__/page-component.test.ts index 3c24d041e..66c19ff06 100644 --- a/packages/services/src/page-component/__tests__/page-component.test.ts +++ b/packages/services/src/page-component/__tests__/page-component.test.ts @@ -6,8 +6,7 @@ import { pageComponentGroup, workspace, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { expectAuditRow, diff --git a/packages/services/src/page-subscriber/__tests__/page-subscriber.test.ts b/packages/services/src/page-subscriber/__tests__/page-subscriber.test.ts index b453e054f..563db004f 100644 --- a/packages/services/src/page-subscriber/__tests__/page-subscriber.test.ts +++ b/packages/services/src/page-subscriber/__tests__/page-subscriber.test.ts @@ -9,16 +9,16 @@ import { createPage, createPageComponent, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; import { afterAll, afterEach, beforeAll, beforeEach, describe, + expect, test, -} from "@std/testing/bdd"; -import { stub } from "@std/testing/mock"; + vi, +} from "vitest"; import { clearAuditLog, @@ -482,7 +482,7 @@ describe("upsertSelfSignupSubscriber", () => { test("rejects when the workspace plan disables status-subscribers", async () => { // Rolled-back tx so the free-workspace page never commits — a // committed page would trip `page.test.ts`'s free-plan - // `status-pages` quota assertions under `deno test --parallel`. + // `status-pages` quota assertions when the suites run in parallel. await withTestTransaction(async (tx) => { const freePage = await tx .insert(page) @@ -540,18 +540,20 @@ describe("verifySelfSignupSubscriber", () => { | undefined; const transaction = db.transaction.bind(db); // Commit the competing signup after token lookup, before acceptance starts. - const intercept = stub(db, "transaction", async (fn, config) => { - intercept.restore(); - expanded = await upsertSelfSignupSubscriber({ - visitor: null, - input: { - email: first.email, - pageId: PAGE_ID, - componentIds: scope === "entire page" ? [] : [COMPONENT_2], - }, + const intercept = vi + .spyOn(db, "transaction") + .mockImplementation(async (fn, config) => { + intercept.mockRestore(); + expanded = await upsertSelfSignupSubscriber({ + visitor: null, + input: { + email: first.email, + pageId: PAGE_ID, + componentIds: scope === "entire page" ? [] : [COMPONENT_2], + }, + }); + return transaction(fn, config); }); - return transaction(fn, config); - }); try { const result = await verifySelfSignupSubscriber({ input: { token: first.token, domain: PAGE_SLUG }, @@ -581,7 +583,7 @@ describe("verifySelfSignupSubscriber", () => { : [COMPONENT_1, COMPONENT_2].sort((a, b) => a - b), ); } finally { - if (!intercept.restored) intercept.restore(); + intercept.mockRestore(); await db.delete(pageSubscriber).where(eq(pageSubscriber.id, first.id)); await clearAuditLogFor({ entityType: "page_subscriber", diff --git a/packages/services/src/page-subscriber/__tests__/slack.test.ts b/packages/services/src/page-subscriber/__tests__/slack.test.ts index d34beb14b..4a34fe7ae 100644 --- a/packages/services/src/page-subscriber/__tests__/slack.test.ts +++ b/packages/services/src/page-subscriber/__tests__/slack.test.ts @@ -1,8 +1,7 @@ import { db, eq, inArray } from "@openstatus/db"; import { pageSubscriber } from "@openstatus/db/src/schema"; import { createPage } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, expectAuditRow } from "../../../test/helpers"; import { diff --git a/packages/services/src/page/__tests__/page.test.ts b/packages/services/src/page/__tests__/page.test.ts index 3d248694f..2c4ad4de0 100644 --- a/packages/services/src/page/__tests__/page.test.ts +++ b/packages/services/src/page/__tests__/page.test.ts @@ -8,8 +8,7 @@ import { workspace, } from "@openstatus/db/src/schema"; import { getLimits } from "@openstatus/db/src/schema/plan/utils"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/private-location/__tests__/private-location.test.ts b/packages/services/src/private-location/__tests__/private-location.test.ts index e103fb5f6..410ddea33 100644 --- a/packages/services/src/private-location/__tests__/private-location.test.ts +++ b/packages/services/src/private-location/__tests__/private-location.test.ts @@ -7,8 +7,7 @@ import { workspace, } from "@openstatus/db/src/schema"; import { getLimits } from "@openstatus/db/src/schema/plan/utils"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/slack-user/__tests__/slack-user.test.ts b/packages/services/src/slack-user/__tests__/slack-user.test.ts index 166f043b8..576497129 100644 --- a/packages/services/src/slack-user/__tests__/slack-user.test.ts +++ b/packages/services/src/slack-user/__tests__/slack-user.test.ts @@ -4,8 +4,7 @@ import { addUserToWorkspace, createUser, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/sso/__tests__/sso.test.ts b/packages/services/src/sso/__tests__/sso.test.ts index 686f6fe0c..02785041a 100644 --- a/packages/services/src/sso/__tests__/sso.test.ts +++ b/packages/services/src/sso/__tests__/sso.test.ts @@ -8,8 +8,7 @@ import { addUserToWorkspace, createUser, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/status-report/__tests__/attribution.test.ts b/packages/services/src/status-report/__tests__/attribution.test.ts index da91cd0a4..326942d8a 100644 --- a/packages/services/src/status-report/__tests__/attribution.test.ts +++ b/packages/services/src/status-report/__tests__/attribution.test.ts @@ -5,8 +5,7 @@ import { statusReportUpdate, } from "@openstatus/db/src/schema"; import { createUser } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/status-report/__tests__/derive-status.test.ts b/packages/services/src/status-report/__tests__/derive-status.test.ts index fe6560ad4..c33639a72 100644 --- a/packages/services/src/status-report/__tests__/derive-status.test.ts +++ b/packages/services/src/status-report/__tests__/derive-status.test.ts @@ -4,8 +4,7 @@ import { statusReport, statusReportUpdate, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { afterAll, beforeAll, describe, test } from "@std/testing/bdd"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/status-report/__tests__/status-report.test.ts b/packages/services/src/status-report/__tests__/status-report.test.ts index 2708634e2..ea5c0ae6f 100644 --- a/packages/services/src/status-report/__tests__/status-report.test.ts +++ b/packages/services/src/status-report/__tests__/status-report.test.ts @@ -8,14 +8,14 @@ import { statusReportUpdate, statusReportUpdateToPageComponents, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; import { afterAll, beforeAll, beforeEach, describe, + expect, test, -} from "@std/testing/bdd"; +} from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/src/status-timeline/__tests__/downtime.test.ts b/packages/services/src/status-timeline/__tests__/downtime.test.ts index 61300a8f7..d67943e55 100644 --- a/packages/services/src/status-timeline/__tests__/downtime.test.ts +++ b/packages/services/src/status-timeline/__tests__/downtime.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { type WeightedInterval, mergedDowntimeMs } from "../downtime"; diff --git a/packages/services/src/status-timeline/__tests__/events.test.ts b/packages/services/src/status-timeline/__tests__/events.test.ts index 2fb8bf9ee..c7814f115 100644 --- a/packages/services/src/status-timeline/__tests__/events.test.ts +++ b/packages/services/src/status-timeline/__tests__/events.test.ts @@ -1,6 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; -import { FakeTime } from "@std/testing/time"; +import { describe, expect, test, vi } from "vitest"; import { activeReportStatus, @@ -104,7 +102,7 @@ describe("getEvents incident timestamps", () => { describe("getEvents lookback", () => { test("keeps overlapping intervals and clips incident downtime to the window", () => { - const time = new FakeTime(day("2026-03-01")); + vi.useFakeTimers({ now: day("2026-03-01") }); try { const from = day("2025-12-01"); const threshold = day("2026-01-15"); @@ -169,8 +167,8 @@ describe("getEvents lookback", () => { expect( durationDowntimeMs(events, { start: threshold.getTime(), - end: time.now, - now: time.now, + end: Date.now(), + now: Date.now(), }), ).toBe(3_888_000_000); expect( @@ -180,7 +178,7 @@ describe("getEvents lookback", () => { ).status, ).toBe("maintenance"); } finally { - time.restore(); + vi.useRealTimers(); } }); }); diff --git a/packages/services/src/status-timeline/__tests__/uptime.test.ts b/packages/services/src/status-timeline/__tests__/uptime.test.ts index 81f9cfebb..c81c2ce18 100644 --- a/packages/services/src/status-timeline/__tests__/uptime.test.ts +++ b/packages/services/src/status-timeline/__tests__/uptime.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import type { Event } from "../events"; import { diff --git a/packages/services/src/workspace/__tests__/billing.test.ts b/packages/services/src/workspace/__tests__/billing.test.ts index 2161d9e9c..8ba9e06d5 100644 --- a/packages/services/src/workspace/__tests__/billing.test.ts +++ b/packages/services/src/workspace/__tests__/billing.test.ts @@ -6,8 +6,7 @@ import { createTestWorkspace, createUser, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { ZodError } from "zod"; import { diff --git a/packages/services/src/workspace/__tests__/downgrade.test.ts b/packages/services/src/workspace/__tests__/downgrade.test.ts index 669e7f4b7..fd748620b 100644 --- a/packages/services/src/workspace/__tests__/downgrade.test.ts +++ b/packages/services/src/workspace/__tests__/downgrade.test.ts @@ -10,8 +10,7 @@ import { workspace, } from "@openstatus/db/src/schema"; import { getLimits } from "@openstatus/db/src/schema/plan/utils"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { expectAuditRow, diff --git a/packages/services/src/workspace/__tests__/trial.test.ts b/packages/services/src/workspace/__tests__/trial.test.ts index 614cbe47b..0d21f4dd4 100644 --- a/packages/services/src/workspace/__tests__/trial.test.ts +++ b/packages/services/src/workspace/__tests__/trial.test.ts @@ -4,8 +4,7 @@ import { createUser, createWorkspace, } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { findTrialEligibleWorkspace, diff --git a/packages/services/src/workspace/__tests__/workspace.test.ts b/packages/services/src/workspace/__tests__/workspace.test.ts index 2825c81fa..16efd35a9 100644 --- a/packages/services/src/workspace/__tests__/workspace.test.ts +++ b/packages/services/src/workspace/__tests__/workspace.test.ts @@ -9,8 +9,7 @@ import { workspace, } from "@openstatus/db/src/schema"; import { getLimits } from "@openstatus/db/src/schema/plan/utils"; -import { expect } from "@std/expect"; -import { beforeAll, describe, test } from "@std/testing/bdd"; +import { beforeAll, describe, expect, test } from "vitest"; import { createWorkspaceFixture, diff --git a/packages/services/test/helpers.ts b/packages/services/test/helpers.ts index 319c430da..9195434ed 100644 --- a/packages/services/test/helpers.ts +++ b/packages/services/test/helpers.ts @@ -5,7 +5,7 @@ import { selectWorkspaceSchema, } from "@openstatus/db/src/schema"; import { createTestWorkspace } from "@openstatus/db/src/test/factories"; -import { expect } from "@std/expect"; +import { expect } from "vitest"; import type { Actor, DB, DrizzleTx, ServiceContext } from "../src/context"; import type { Workspace } from "../src/types"; diff --git a/packages/services/tsconfig.json b/packages/services/tsconfig.json index 9af4d8dfa..0da708f7f 100644 --- a/packages/services/tsconfig.json +++ b/packages/services/tsconfig.json @@ -1,17 +1,7 @@ { "extends": "@openstatus/tsconfig/base.json", "include": ["src", "test"], - "exclude": [ - "dist", - "build", - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ], + "exclude": ["dist", "build", "node_modules"], "compilerOptions": { "module": "ESNext", "moduleResolution": "bundler" diff --git a/packages/services/vitest.config.ts b/packages/services/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/services/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/status-fetcher/__tests__/detect.test.ts b/packages/status-fetcher/__tests__/detect.test.ts index e1f465fa4..f55e34baa 100644 --- a/packages/status-fetcher/__tests__/detect.test.ts +++ b/packages/status-fetcher/__tests__/detect.test.ts @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { Effect } from "effect"; +import { describe, expect, it } from "vitest"; import { detectProvider } from "../src/detect"; import { installMockFetch } from "./helpers"; @@ -70,7 +69,7 @@ describe("detectProvider", () => { expect(result.currentProviderValidated).toBe(false); expect(result.matches.map((m) => m.provider)).toEqual(["instatus"]); expect(result.hostnameSuggestions).toEqual([]); - expect(fetchMock.calls.length).toBe(3); + expect(fetchMock.mock.calls.length).toBe(3); }); it("derives probe endpoints from urls with query or trailing slash", async () => { @@ -96,7 +95,7 @@ describe("detectProvider", () => { ); expect(result.currentProviderValidated).toBe(true); expect(result.matches).toEqual([]); - expect(fetchMock.calls.length).toBe(3); + expect(fetchMock.mock.calls.length).toBe(3); }); it("accepts pair label drift: short-circuits even when the page is served by the api-identical peer", async () => { @@ -115,7 +114,7 @@ describe("detectProvider", () => { ); expect(result.currentProviderValidated).toBe(true); expect(result.matches).toEqual([]); - expect(fetchMock.calls.length).toBe(3); + expect(fetchMock.mock.calls.length).toBe(3); }); it("tiebreaks the ambiguous pair to atlassian via html marker", async () => { @@ -242,7 +241,7 @@ describe("detectProvider", () => { "hostname tiebreak: atlassian-statuspage", ); // 3 probes at the old base, the html fetch, 3 probes at the new base - expect(fetchMock.calls.length).toBe(7); + expect(fetchMock.mock.calls.length).toBe(7); }); it("probes the new origin root when the redirect lands on a subpath", async () => { @@ -274,7 +273,7 @@ describe("detectProvider", () => { detectProvider({ statusPageUrl: PAGE_URL, currentProvider: "unknown" }), ); expect(result.movedTo).toBeUndefined(); - expect(fetchMock.calls.length).toBe(4); + expect(fetchMock.mock.calls.length).toBe(4); }); it("suggests instatus from an html marker when nothing validates", async () => { diff --git a/packages/status-fetcher/__tests__/fetch.test.ts b/packages/status-fetcher/__tests__/fetch.test.ts index fef8d9913..e84d79b8c 100644 --- a/packages/status-fetcher/__tests__/fetch.test.ts +++ b/packages/status-fetcher/__tests__/fetch.test.ts @@ -1,7 +1,5 @@ -import { expect } from "@std/expect"; -import { beforeEach, describe, it } from "@std/testing/bdd"; -import { spy } from "@std/testing/mock"; import { Cause, Effect, Exit, Option } from "effect"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import { z } from "zod"; import { @@ -16,7 +14,7 @@ const TEST_URL = "https://api.example.com/status"; type FetchImpl = (url: string, init?: RequestInit) => Promise; const installMockFetch = (impl: FetchImpl) => { - const fn = spy(impl); + const fn = vi.fn(impl); globalThis.fetch = fn as unknown as typeof fetch; return fn; }; @@ -86,7 +84,7 @@ describe("fetchJson", () => { expect(err.kind).toBe("http"); expect(err.fetcherName).toBe("test"); expect(err.entryId).toBe("x"); - expect(fetchMock.calls.length).toBe(1); + expect(fetchMock.mock.calls.length).toBe(1); }); it("retries up to 3 times on 5xx then fails", async () => { @@ -99,7 +97,7 @@ describe("fetchJson", () => { ); const err = expectFailure(exit); expect(err.httpStatus).toBe(503); - expect(fetchMock.calls.length).toBe(4); + expect(fetchMock.mock.calls.length).toBe(4); }); it("retries up to 3 times on network error then fails", async () => { @@ -113,7 +111,7 @@ describe("fetchJson", () => { const err = expectFailure(exit); expect(err.httpStatus).toBeUndefined(); expect(err.kind).toBe("network"); - expect(fetchMock.calls.length).toBe(4); + expect(fetchMock.mock.calls.length).toBe(4); }); it("fails with FetchError on schema mismatch without retrying", async () => { @@ -128,7 +126,7 @@ describe("fetchJson", () => { expect(err.cause).toBeInstanceOf(Error); expect(err.kind).toBe("schema"); expect(err.message).toBe(`[FetchError] schema mismatch: ${TEST_URL}`); - expect(fetchMock.calls.length).toBe(1); + expect(fetchMock.mock.calls.length).toBe(1); }); it("classifies a non-JSON 200 body as parse", async () => { @@ -236,7 +234,7 @@ describe("fetchText", () => { const exit = await Effect.runPromiseExit(fetchText({ url: TEST_URL })); const err = expectFailure(exit); expect(err.httpStatus).toBe(500); - expect(fetchMock.calls.length).toBe(4); + expect(fetchMock.mock.calls.length).toBe(4); }); }); diff --git a/packages/status-fetcher/__tests__/fetchers/atlassian.test.ts b/packages/status-fetcher/__tests__/fetchers/atlassian.test.ts index e7adf54ed..e3e0251d3 100644 --- a/packages/status-fetcher/__tests__/fetchers/atlassian.test.ts +++ b/packages/status-fetcher/__tests__/fetchers/atlassian.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { beforeEach, describe, it } from "@std/testing/bdd"; +import { beforeEach, describe, expect, it } from "vitest"; import { AtlassianFetcher } from "../../src/fetchers/atlassian"; import type { StatusPageEntry } from "../../src/types"; @@ -114,11 +113,11 @@ describe("AtlassianFetcher", () => { expect(result.description).toBe("All Systems Operational"); expect(result.timezone).toBe("Etc/UTC"); expect(typeof result.updated_at).toBe("number"); - const call = fetchMock.calls[fetchMock.calls.length - 1]; - expect(call.args[0]).toBe( + const call = fetchMock.mock.lastCall; + expect(call?.[0]).toBe( "https://www.githubstatus.com/api/v2/summary.json", ); - expect(call.args[1]).toEqual( + expect(call?.[1]).toEqual( expect.objectContaining({ headers: expect.objectContaining({ "User-Agent": "OpenStatus-Directory/1.0", @@ -238,9 +237,9 @@ describe("AtlassianFetcher", () => { await runFetcher(fetcher, entry); - const call = fetchMock.calls[fetchMock.calls.length - 1]; - expect(call.args[0]).toBe("https://custom.endpoint.com/status.json"); - expect(call.args[1]).toEqual(expect.any(Object)); + const call = fetchMock.mock.lastCall; + expect(call?.[0]).toBe("https://custom.endpoint.com/status.json"); + expect(call?.[1]).toEqual(expect.any(Object)); }); it("should fail with FetchError on non-200 response", async () => { @@ -335,11 +334,11 @@ describe("AtlassianFetcher", () => { const incidents = await runIncidents(fetcher, entry); - const call = fetchMock.calls[fetchMock.calls.length - 1]; - expect(call.args[0]).toBe( + const call = fetchMock.mock.lastCall; + expect(call?.[0]).toBe( "https://www.githubstatus.com/api/v2/incidents.json", ); - expect(call.args[1]).toEqual(expect.any(Object)); + expect(call?.[1]).toEqual(expect.any(Object)); expect(incidents).toHaveLength(2); expect(incidents[0]).toMatchObject({ providerIncidentId: "abc", diff --git a/packages/status-fetcher/__tests__/fetchers/betterstack.test.ts b/packages/status-fetcher/__tests__/fetchers/betterstack.test.ts index d1bb0869a..693d5fa43 100644 --- a/packages/status-fetcher/__tests__/fetchers/betterstack.test.ts +++ b/packages/status-fetcher/__tests__/fetchers/betterstack.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { beforeEach, describe, it } from "@std/testing/bdd"; +import { beforeEach, describe, expect, it } from "vitest"; import { BetterStackFetcher } from "../../src/fetchers/betterstack"; import type { StatusPageEntry } from "../../src/types"; @@ -122,9 +121,9 @@ describe("BetterStackFetcher", () => { expect(result.description).toBe("All Systems Operational"); expect(result.timezone).toBe("America/New_York"); expect(typeof result.updated_at).toBe("number"); - const call = fetchMock.calls[fetchMock.calls.length - 1]; - expect(call.args[0]).toBe("https://status.test.com/index.json"); - expect(call.args[1]).toEqual( + const call = fetchMock.mock.lastCall; + expect(call?.[0]).toBe("https://status.test.com/index.json"); + expect(call?.[1]).toEqual( expect.objectContaining({ headers: expect.objectContaining({ "User-Agent": "OpenStatus-Directory/1.0", @@ -242,9 +241,9 @@ describe("BetterStackFetcher", () => { await runFetcher(fetcher, entry); - const call = fetchMock.calls[fetchMock.calls.length - 1]; - expect(call.args[0]).toBe("https://custom.endpoint.com/status.json"); - expect(call.args[1]).toEqual(expect.any(Object)); + const call = fetchMock.mock.lastCall; + expect(call?.[0]).toBe("https://custom.endpoint.com/status.json"); + expect(call?.[1]).toEqual(expect.any(Object)); }); it("should fail with FetchError on 4xx response", async () => { diff --git a/packages/status-fetcher/__tests__/fetchers/components.test.ts b/packages/status-fetcher/__tests__/fetchers/components.test.ts index d87f3ae31..45c355bef 100644 --- a/packages/status-fetcher/__tests__/fetchers/components.test.ts +++ b/packages/status-fetcher/__tests__/fetchers/components.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { beforeEach, describe, it } from "@std/testing/bdd"; +import { beforeEach, describe, expect, it } from "vitest"; import { AtlassianFetcher } from "../../src/fetchers/atlassian"; import { IncidentioFetcher } from "../../src/fetchers/incidentio"; @@ -82,11 +81,9 @@ function itNormalizes( const components = await runComponents(fetcher, entry); - const call = fetchMock.calls[fetchMock.calls.length - 1]; - expect(call.args[0]).toBe( - `${entry.status_page_url}/api/v2/components.json`, - ); - expect(call.args[1]).toEqual(expect.any(Object)); + const call = fetchMock.mock.lastCall; + expect(call?.[0]).toBe(`${entry.status_page_url}/api/v2/components.json`); + expect(call?.[1]).toEqual(expect.any(Object)); // group container "grp" is excluded; only the three leaves remain expect(components).toHaveLength(3); diff --git a/packages/status-fetcher/__tests__/fetchers/custom.test.ts b/packages/status-fetcher/__tests__/fetchers/custom.test.ts index a30914704..4b47fd333 100644 --- a/packages/status-fetcher/__tests__/fetchers/custom.test.ts +++ b/packages/status-fetcher/__tests__/fetchers/custom.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { beforeEach, describe, it } from "@std/testing/bdd"; +import { beforeEach, describe, expect, it } from "vitest"; import { CustomApiFetcher } from "../../src/fetchers/custom"; import type { StatusPageEntry } from "../../src/types"; diff --git a/packages/status-fetcher/__tests__/fetchers/edge-cases.test.ts b/packages/status-fetcher/__tests__/fetchers/edge-cases.test.ts index 01226d4f5..c81c92172 100644 --- a/packages/status-fetcher/__tests__/fetchers/edge-cases.test.ts +++ b/packages/status-fetcher/__tests__/fetchers/edge-cases.test.ts @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { Exit } from "effect"; +import { describe, expect, it } from "vitest"; import { AtlassianFetcher } from "../../src/fetchers/atlassian"; import { BetterStackFetcher } from "../../src/fetchers/betterstack"; diff --git a/packages/status-fetcher/__tests__/fetchers/html.test.ts b/packages/status-fetcher/__tests__/fetchers/html.test.ts index ddbac6a13..73153fab4 100644 --- a/packages/status-fetcher/__tests__/fetchers/html.test.ts +++ b/packages/status-fetcher/__tests__/fetchers/html.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { beforeEach, describe, it } from "@std/testing/bdd"; +import { beforeEach, describe, expect, it } from "vitest"; import { HtmlScraperFetcher } from "../../src/fetchers/html"; import type { StatusPageEntry } from "../../src/types"; @@ -93,9 +92,9 @@ describe("HtmlScraperFetcher", () => { expect(result.description).toBe("All Systems Operational"); expect(result.timezone).toBe("UTC"); expect(typeof result.updated_at).toBe("number"); - const call = fetchMock.calls[fetchMock.calls.length - 1]; - expect(call.args[0]).toBe("https://status.test.com"); - expect(call.args[1]).toEqual( + const call = fetchMock.mock.lastCall; + expect(call?.[0]).toBe("https://status.test.com"); + expect(call?.[1]).toEqual( expect.objectContaining({ headers: expect.objectContaining({ "User-Agent": "Mozilla/5.0 (compatible; OpenStatus-Bot/1.0)", diff --git a/packages/status-fetcher/__tests__/fetchers/incidentio.test.ts b/packages/status-fetcher/__tests__/fetchers/incidentio.test.ts index 6a759fdd1..2bf33fb17 100644 --- a/packages/status-fetcher/__tests__/fetchers/incidentio.test.ts +++ b/packages/status-fetcher/__tests__/fetchers/incidentio.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { beforeEach, describe, it } from "@std/testing/bdd"; +import { beforeEach, describe, expect, it } from "vitest"; import { IncidentioFetcher } from "../../src/fetchers/incidentio"; import type { StatusPageEntry } from "../../src/types"; @@ -112,9 +111,9 @@ describe("IncidentioFetcher", () => { expect(result.status).toBe("operational"); expect(result.description).toBe("All Systems Operational"); expect(typeof result.updated_at).toBe("number"); - const call = fetchMock.calls[fetchMock.calls.length - 1]; - expect(call.args[0]).toBe("https://status.test.com/api/v2/summary.json"); - expect(call.args[1]).toEqual( + const call = fetchMock.mock.lastCall; + expect(call?.[0]).toBe("https://status.test.com/api/v2/summary.json"); + expect(call?.[1]).toEqual( expect.objectContaining({ headers: expect.objectContaining({ "User-Agent": "OpenStatus-Directory/1.0", @@ -236,9 +235,9 @@ describe("IncidentioFetcher", () => { await runFetcher(fetcher, entry); - const call = fetchMock.calls[fetchMock.calls.length - 1]; - expect(call.args[0]).toBe("https://custom.endpoint.com/summary.json"); - expect(call.args[1]).toEqual(expect.any(Object)); + const call = fetchMock.mock.lastCall; + expect(call?.[0]).toBe("https://custom.endpoint.com/summary.json"); + expect(call?.[1]).toEqual(expect.any(Object)); }); it("should fail with FetchError on 5xx response", async () => { @@ -324,11 +323,9 @@ describe("IncidentioFetcher", () => { const incidents = await runIncidents(fetcher, entry); - const call = fetchMock.calls[fetchMock.calls.length - 1]; - expect(call.args[0]).toBe( - "https://status.linear.app/api/v2/incidents.json", - ); - expect(call.args[1]).toEqual(expect.any(Object)); + const call = fetchMock.mock.lastCall; + expect(call?.[0]).toBe("https://status.linear.app/api/v2/incidents.json"); + expect(call?.[1]).toEqual(expect.any(Object)); expect(incidents).toHaveLength(1); expect(incidents[0]).toMatchObject({ providerIncidentId: "INC-1", diff --git a/packages/status-fetcher/__tests__/fetchers/instatus.test.ts b/packages/status-fetcher/__tests__/fetchers/instatus.test.ts index fefc068bf..8d19e6f25 100644 --- a/packages/status-fetcher/__tests__/fetchers/instatus.test.ts +++ b/packages/status-fetcher/__tests__/fetchers/instatus.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { beforeEach, describe, it } from "@std/testing/bdd"; +import { beforeEach, describe, expect, it } from "vitest"; import { InstatusFetcher } from "../../src/fetchers/instatus"; import type { StatusPageEntry } from "../../src/types"; @@ -106,9 +105,9 @@ describe("InstatusFetcher", () => { expect(result.description).toBe("All Systems Operational"); expect(result.timezone).toBe("UTC"); expect(typeof result.updated_at).toBe("number"); - const call = fetchMock.calls[fetchMock.calls.length - 1]; - expect(call.args[0]).toBe("https://test.instatus.com/summary.json"); - expect(call.args[1]).toEqual( + const call = fetchMock.mock.lastCall; + expect(call?.[0]).toBe("https://test.instatus.com/summary.json"); + expect(call?.[1]).toEqual( expect.objectContaining({ headers: expect.objectContaining({ "User-Agent": "OpenStatus-Directory/1.0", @@ -229,9 +228,9 @@ describe("InstatusFetcher", () => { }, }); - const call = fetchMock.calls[fetchMock.calls.length - 1]; - expect(call.args[0]).toBe("https://custom.endpoint.com/status.json"); - expect(call.args[1]).toEqual(expect.any(Object)); + const call = fetchMock.mock.lastCall; + expect(call?.[0]).toBe("https://custom.endpoint.com/status.json"); + expect(call?.[1]).toEqual(expect.any(Object)); }); it("should fail with FetchError on non-200 response (after retries)", async () => { diff --git a/packages/status-fetcher/__tests__/fetchers/uptimerobot.test.ts b/packages/status-fetcher/__tests__/fetchers/uptimerobot.test.ts index 0169bd4d9..8f49ed8e5 100644 --- a/packages/status-fetcher/__tests__/fetchers/uptimerobot.test.ts +++ b/packages/status-fetcher/__tests__/fetchers/uptimerobot.test.ts @@ -1,6 +1,4 @@ -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, it } from "@std/testing/bdd"; -import { spy } from "@std/testing/mock"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { UptimeRobotFetcher } from "../../src/fetchers/uptimerobot"; import type { StatusPageEntry } from "../../src/types"; @@ -140,9 +138,9 @@ describe("UptimeRobotFetcher", () => { expect(result.description).toBe("0 monitors down (3 total)"); expect(result.timezone).toBe("UTC"); expect(typeof result.updated_at).toBe("number"); - const lastCall = fetchMock.calls[fetchMock.calls.length - 1]; - expect(lastCall.args[0]).toBe(ENDPOINT); - expect(lastCall.args[1]).toEqual( + const lastCall = fetchMock.mock.lastCall; + expect(lastCall?.[0]).toBe(ENDPOINT); + expect(lastCall?.[1]).toEqual( expect.objectContaining({ headers: expect.objectContaining({ "User-Agent": "OpenStatus-Directory/1.0", @@ -275,7 +273,7 @@ describe("UptimeRobotFetcher", () => { }); it("unknown statusClass folds to operational with a warn", async () => { - const warnSpy = spy(() => {}); + const warnSpy = vi.fn(() => {}); const originalWarn = console.warn; console.warn = warnSpy; @@ -290,7 +288,7 @@ describe("UptimeRobotFetcher", () => { expect(result.severity).toBe("none"); expect(result.status).toBe("operational"); - expect(warnSpy.calls.length).toBeGreaterThan(0); + expect(warnSpy.mock.calls.length).toBeGreaterThan(0); } finally { console.warn = originalWarn; } diff --git a/packages/status-fetcher/__tests__/helpers.ts b/packages/status-fetcher/__tests__/helpers.ts index 05cde49f5..a38850086 100644 --- a/packages/status-fetcher/__tests__/helpers.ts +++ b/packages/status-fetcher/__tests__/helpers.ts @@ -1,5 +1,5 @@ -import { spy } from "@std/testing/mock"; import { Cause, Effect, Exit, Option } from "effect"; +import { vi } from "vitest"; import type { FetchError } from "../src/fetch"; import type { @@ -13,7 +13,7 @@ import type { type FetchImpl = (url: string, init?: RequestInit) => Promise; export const installMockFetch = (impl: FetchImpl) => { - const fn = spy(impl); + const fn = vi.fn(impl); globalThis.fetch = fn as unknown as typeof fetch; return fn; }; diff --git a/packages/status-fetcher/__tests__/integration.test.ts b/packages/status-fetcher/__tests__/integration.test.ts index 546292208..d8accd042 100644 --- a/packages/status-fetcher/__tests__/integration.test.ts +++ b/packages/status-fetcher/__tests__/integration.test.ts @@ -1,6 +1,5 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; import { Effect } from "effect"; +import { describe, expect, it } from "vitest"; import { fetchers as allFetchers } from "../src/fetchers"; import type { StatusPageEntry, StatusPageProvider } from "../src/types"; diff --git a/packages/status-fetcher/__tests__/utils.test.ts b/packages/status-fetcher/__tests__/utils.test.ts index f2d78f56b..a9341b298 100644 --- a/packages/status-fetcher/__tests__/utils.test.ts +++ b/packages/status-fetcher/__tests__/utils.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { inferStatus, urlHostnameEndsWith } from "../src/utils"; diff --git a/packages/status-fetcher/package.json b/packages/status-fetcher/package.json index c291e86fd..dfe89f3c8 100644 --- a/packages/status-fetcher/package.json +++ b/packages/status-fetcher/package.json @@ -12,8 +12,8 @@ }, "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports", - "test:watch": "deno test --parallel -A --no-check --sloppy-imports --watch", + "test": "vitest run", + "test:watch": "vitest", "tsc": "tsc --noEmit" }, "dependencies": { @@ -22,8 +22,8 @@ "zod": "catalog:" }, "devDependencies": { - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", - "typescript": "catalog:" + "@openstatus/test-utils": "workspace:*", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/status-fetcher/tsconfig.json b/packages/status-fetcher/tsconfig.json index 55b77accc..32f1f6413 100644 --- a/packages/status-fetcher/tsconfig.json +++ b/packages/status-fetcher/tsconfig.json @@ -11,14 +11,5 @@ "resolveJsonModule": true, "isolatedModules": true }, - "include": ["src/**/*", "__tests__/**/*"], - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + "include": ["src/**/*", "__tests__/**/*"] } diff --git a/packages/status-fetcher/vitest.config.ts b/packages/status-fetcher/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/status-fetcher/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/subscriptions/package.json b/packages/subscriptions/package.json index 734741695..ce9d09f8e 100644 --- a/packages/subscriptions/package.json +++ b/packages/subscriptions/package.json @@ -16,7 +16,7 @@ }, "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports" + "test": "vitest run" }, "dependencies": { "@openstatus/db": "workspace:*", @@ -29,9 +29,9 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/subscriptions/src/channels/email.test.ts b/packages/subscriptions/src/channels/email.test.ts index eda3ace15..7c616dcd4 100644 --- a/packages/subscriptions/src/channels/email.test.ts +++ b/packages/subscriptions/src/channels/email.test.ts @@ -1,8 +1,14 @@ import "../test-preload.ts"; import { EmailClient } from "@openstatus/emails"; -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, type Stub, stub } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import type { PageUpdate, Subscription } from "../types"; import { @@ -14,8 +20,8 @@ import { // RESEND_API_KEY is set in test-preload.ts (see bunfig.toml) so @openstatus/emails // loads successfully and EmailClient prototype methods can be spied on. -let sendPageSubscriptionMock: Stub; -let sendPageUpdateMock: Stub; +let sendPageSubscriptionMock: MockInstance; +let sendPageUpdateMock: MockInstance; function makeSub(overrides: Partial = {}): Subscription { return { @@ -46,19 +52,17 @@ function makeUpdate(overrides: Partial = {}): PageUpdate { } beforeEach(() => { - sendPageSubscriptionMock = stub( - EmailClient.prototype, - "sendPageSubscription", - () => Promise.resolve(undefined), - ); - sendPageUpdateMock = stub(EmailClient.prototype, "sendPageUpdate", () => - Promise.resolve(undefined), - ); + sendPageSubscriptionMock = vi + .spyOn(EmailClient.prototype, "sendPageSubscription") + .mockImplementation(() => Promise.resolve(undefined)); + sendPageUpdateMock = vi + .spyOn(EmailClient.prototype, "sendPageUpdate") + .mockImplementation(() => Promise.resolve(undefined)); }); afterEach(() => { - sendPageSubscriptionMock.restore(); - sendPageUpdateMock.restore(); + sendPageSubscriptionMock.mockRestore(); + sendPageUpdateMock.mockRestore(); }); // ─── validateEmailConfig ────────────────────────────────────────────────────── @@ -100,8 +104,8 @@ describe("sendEmailVerification", () => { const sub = makeSub({ email: "user@example.com", pageName: "My Page" }); await sendEmailVerification(sub, "https://example.com/verify/abc"); - assertSpyCalls(sendPageSubscriptionMock, 1); - const [args] = sendPageSubscriptionMock.calls[0].args; + expect(sendPageSubscriptionMock).toHaveBeenCalledTimes(1); + const [args] = sendPageSubscriptionMock.mock.calls[0]; expect(args.to).toBe("user@example.com"); expect(args.link).toBe("https://example.com/verify/abc"); expect(args.page).toBe("My Page"); @@ -113,13 +117,13 @@ describe("sendEmailVerification", () => { describe("sendEmailNotifications", () => { test("does nothing for an empty subscriptions array", async () => { await sendEmailNotifications([], makeUpdate()); - assertSpyCalls(sendPageUpdateMock, 0); + expect(sendPageUpdateMock).toHaveBeenCalledTimes(0); }); test("filters out subscriptions without an email address", async () => { const sub = makeSub({ email: undefined }); await sendEmailNotifications([sub], makeUpdate()); - assertSpyCalls(sendPageUpdateMock, 0); + expect(sendPageUpdateMock).toHaveBeenCalledTimes(0); }); test("calls sendPageUpdate once with all valid subscribers", async () => { @@ -129,8 +133,8 @@ describe("sendEmailNotifications", () => { await sendEmailNotifications([sub1, sub2], update); - assertSpyCalls(sendPageUpdateMock, 1); - const [args] = sendPageUpdateMock.calls[0].args; + expect(sendPageUpdateMock).toHaveBeenCalledTimes(1); + const [args] = sendPageUpdateMock.mock.calls[0]; expect(args.subscribers).toHaveLength(2); expect(args.subscribers[0].email).toBe("a@example.com"); expect(args.subscribers[1].email).toBe("b@example.com"); @@ -144,7 +148,7 @@ describe("sendEmailNotifications", () => { await sendEmailNotifications([sub], update); - const [args] = sendPageUpdateMock.calls[0].args; + const [args] = sendPageUpdateMock.mock.calls[0]; expect(args.pageComponents).toEqual(["API", "Database"]); }); @@ -152,7 +156,7 @@ describe("sendEmailNotifications", () => { const sub = makeSub(); await sendEmailNotifications([sub], makeUpdate({ updateId: 77 })); - const [args] = sendPageUpdateMock.calls[0].args; + const [args] = sendPageUpdateMock.mock.calls[0]; expect(args.idempotencyKey).toMatch(/^status-report-update:77:[0-9a-z]+$/); }); @@ -163,7 +167,7 @@ describe("sendEmailNotifications", () => { makeUpdate({ id: 17, updateId: undefined, status: "maintenance" }), ); - const [args] = sendPageUpdateMock.calls[0].args; + const [args] = sendPageUpdateMock.mock.calls[0]; expect(args.idempotencyKey).toMatch( /^page-update:17:maintenance:[0-9a-z]+$/, ); @@ -174,7 +178,7 @@ describe("sendEmailNotifications", () => { await sendEmailNotifications([makeSub()], update); await sendEmailNotifications([makeSub()], update); - const keys = sendPageUpdateMock.calls.map((c) => c.args[0].idempotencyKey); + const keys = sendPageUpdateMock.mock.calls.map((c) => c[0].idempotencyKey); expect(keys[0]).toBe(keys[1]); }); @@ -186,7 +190,7 @@ describe("sendEmailNotifications", () => { update, ); - const keys = sendPageUpdateMock.calls.map((c) => c.args[0].idempotencyKey); + const keys = sendPageUpdateMock.mock.calls.map((c) => c[0].idempotencyKey); expect(keys[0]).not.toBe(keys[1]); }); @@ -202,7 +206,7 @@ describe("sendEmailNotifications", () => { }), ); - const [args] = sendPageUpdateMock.calls[0].args; + const [args] = sendPageUpdateMock.mock.calls[0]; expect(args.date).toBe("2026-07-16T11:00:00Z"); expect(args.startsAt).toBe("2026-07-20T10:00:00Z"); expect(args.endsAt).toBe("2026-07-20T11:00:00Z"); @@ -224,7 +228,7 @@ describe("sendEmailNotifications", () => { makeUpdate({ ...base, endsAt: "2026-07-20T12:00:00Z" }), ); - const keys = sendPageUpdateMock.calls.map((c) => c.args[0].idempotencyKey); + const keys = sendPageUpdateMock.mock.calls.map((c) => c[0].idempotencyKey); expect(keys[0]).not.toBe(keys[1]); }); @@ -239,7 +243,7 @@ describe("sendEmailNotifications", () => { makeUpdate({ updateId: 77, date, message: "Root cause identified." }), ); - const keys = sendPageUpdateMock.calls.map((c) => c.args[0].idempotencyKey); + const keys = sendPageUpdateMock.mock.calls.map((c) => c[0].idempotencyKey); expect(keys[0]).not.toBe(keys[1]); }); }); diff --git a/packages/subscriptions/src/channels/index.test.ts b/packages/subscriptions/src/channels/index.test.ts index 7d2263e07..399a21c1d 100644 --- a/packages/subscriptions/src/channels/index.test.ts +++ b/packages/subscriptions/src/channels/index.test.ts @@ -1,6 +1,5 @@ import "../test-preload.ts"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { getChannel } from "./index"; diff --git a/packages/subscriptions/src/channels/slack-blocks.test.ts b/packages/subscriptions/src/channels/slack-blocks.test.ts index d33ece0ad..0a7569b59 100644 --- a/packages/subscriptions/src/channels/slack-blocks.test.ts +++ b/packages/subscriptions/src/channels/slack-blocks.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it as test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import type { PageUpdate, Subscription } from "../types"; import { buildReplyMessage, buildRootMessage } from "./slack-blocks"; diff --git a/packages/subscriptions/src/channels/slack.test.ts b/packages/subscriptions/src/channels/slack.test.ts index 495a7f1f8..a63264b56 100644 --- a/packages/subscriptions/src/channels/slack.test.ts +++ b/packages/subscriptions/src/channels/slack.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it as test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import type { PageUpdate, Subscription } from "../types"; import { diff --git a/packages/subscriptions/src/channels/webhook.test.ts b/packages/subscriptions/src/channels/webhook.test.ts index d17be6f40..ccbcaa85f 100644 --- a/packages/subscriptions/src/channels/webhook.test.ts +++ b/packages/subscriptions/src/channels/webhook.test.ts @@ -1,6 +1,12 @@ -import { expect } from "@std/expect"; -import { afterEach, beforeEach, describe, test } from "@std/testing/bdd"; -import { assertSpyCalls, type Stub, stub } from "@std/testing/mock"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, + type MockInstance, +} from "vitest"; import { webhookPayloadSchema } from "../payload"; import type { PageUpdate, Subscription } from "../types"; @@ -17,7 +23,7 @@ import { const SLACK_URL = "https://hooks.slack.com/services/T1/B1/XXX"; const DISCORD_URL = "https://discord.com/api/webhooks/1/xxx"; -let fetchMock: Stub; +let fetchMock: MockInstance; function setFetch( impl: ( @@ -25,8 +31,8 @@ function setFetch( init?: RequestInit, ) => Promise, ) { - fetchMock?.restore(); - fetchMock = stub(globalThis, "fetch", impl); + fetchMock?.mockRestore(); + fetchMock = vi.spyOn(globalThis, "fetch").mockImplementation(impl); } function resolveFetch(response: Response) { @@ -62,13 +68,15 @@ function makeUpdate(overrides: Partial = {}): PageUpdate { } beforeEach(() => { - fetchMock = stub(globalThis, "fetch", () => - Promise.resolve(new Response(null, { status: 200 })), - ); + fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(() => + Promise.resolve(new Response(null, { status: 200 })), + ); }); afterEach(() => { - fetchMock.restore(); + fetchMock.mockRestore(); }); // ─── validateWebhookConfig ──────────────────────────────────────────────────── @@ -142,8 +150,8 @@ describe("sendWebhookVerification", () => { }); await sendWebhookVerification(sub, "https://example.com/verify/my-token"); - assertSpyCalls(fetchMock, 1); - const [url, init] = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const [url, init] = fetchMock.mock.calls[0]; expect(url).toBe("https://hooks.example.com/sub"); expect(init?.method).toBe("POST"); const body = JSON.parse(init?.body as string); @@ -159,7 +167,7 @@ describe("sendWebhookNotifications", () => { test("skips subscriptions without a webhookUrl", async () => { const sub = makeSub({ webhookUrl: undefined }); await sendWebhookNotifications([sub], makeUpdate()); - assertSpyCalls(fetchMock, 0); + expect(fetchMock).toHaveBeenCalledTimes(0); }); test("sends a POST request to each webhook URL", async () => { @@ -173,8 +181,8 @@ describe("sendWebhookNotifications", () => { await sendWebhookNotifications([sub1, sub2], makeUpdate()); - assertSpyCalls(fetchMock, 2); - const urls = fetchMock.calls.map((c) => c.args[0]); + expect(fetchMock).toHaveBeenCalledTimes(2); + const urls = fetchMock.mock.calls.map((c) => c[0]); expect(urls).toContain("https://hooks.slack.com/services/T1/B1/aaa"); expect(urls).toContain("https://hooks.slack.com/services/T2/B2/bbb"); }); @@ -192,10 +200,10 @@ describe("sendWebhookNotifications", () => { makeUpdate({ updateId: 42 }), ); - assertSpyCalls(fetchMock, 2); + expect(fetchMock).toHaveBeenCalledTimes(2); const byUrl = new Map( - fetchMock.calls.map((c) => { - const [url, init] = c.args as [string, RequestInit]; + fetchMock.mock.calls.map((c) => { + const [url, init] = c as [string, RequestInit]; return [url, JSON.parse(init.body as string)]; }), ); @@ -218,7 +226,7 @@ describe("sendWebhookNotifications", () => { }), ); - const [, init] = fetchMock.calls[0].args; + const [, init] = fetchMock.mock.calls[0]; const sentBody = JSON.parse(init?.body as string); const result = webhookPayloadSchema.safeParse(sentBody); expect(result.success).toBe(true); @@ -238,7 +246,7 @@ describe("sendWebhookNotifications", () => { }), ); - const [, init] = fetchMock.calls[0].args; + const [, init] = fetchMock.mock.calls[0]; const sentBody = JSON.parse(init?.body as string); const result = webhookPayloadSchema.safeParse(sentBody); expect(result.success).toBe(true); @@ -254,7 +262,7 @@ describe("sendWebhookNotifications", () => { await sendWebhookNotifications([sub], makeUpdate()); - const [, init] = fetchMock.calls[0].args; + const [, init] = fetchMock.mock.calls[0]; const headers = init?.headers as Record; expect(headers["X-Custom-Header"]).toBe("my-value"); }); @@ -276,7 +284,7 @@ describe("sendWebhookNotifications", () => { sendWebhookNotifications([sub1, sub2], makeUpdate()), ).resolves.toBeUndefined(); - const calls = fetchMock.calls.map((c) => String(c.args[0])); + const calls = fetchMock.mock.calls.map((c) => String(c[0])); expect(calls).toContain("https://hooks.slack.com/services/T2/B2/succeed"); }); @@ -308,7 +316,7 @@ describe("sendWebhookNotifications", () => { await sendWebhookNotifications([makeSub()], makeUpdate()); - assertSpyCalls(fetchMock, 1); + expect(fetchMock).toHaveBeenCalledTimes(1); }); }); @@ -341,7 +349,7 @@ describe("sendWebhookNotifications (flavor detection)", () => { await sendWebhookNotifications([sub], makeUpdate()); - const [, init] = fetchMock.calls[0].args; + const [, init] = fetchMock.mock.calls[0]; const body = JSON.parse(init?.body as string); expect(body.attachments).toBeDefined(); expect(body.attachments[0].blocks).toBeInstanceOf(Array); @@ -353,7 +361,7 @@ describe("sendWebhookNotifications (flavor detection)", () => { await sendWebhookNotifications([sub], makeUpdate()); - const [, init] = fetchMock.calls[0].args; + const [, init] = fetchMock.mock.calls[0]; const body = JSON.parse(init?.body as string); expect(body.embeds).toBeInstanceOf(Array); expect(body.embeds[0].title).toBe("Test Incident"); @@ -368,7 +376,7 @@ describe("sendWebhookNotifications (flavor detection)", () => { await sendWebhookNotifications([sub], makeUpdate()); - const [, init] = fetchMock.calls[0].args; + const [, init] = fetchMock.mock.calls[0]; const stringified = init?.body as string; expect(stringified).toContain("https://demo.openstatus.dev/manage/tok-123"); expect(stringified).toContain( @@ -385,7 +393,7 @@ describe("sendWebhookNotifications (flavor detection)", () => { await sendWebhookNotifications([sub], makeUpdate()); - const [, init] = fetchMock.calls[0].args; + const [, init] = fetchMock.mock.calls[0]; const stringified = init?.body as string; expect(stringified).toContain("https://status.partner.com/manage/tok-123"); }); @@ -396,7 +404,7 @@ describe("sendWebhookNotifications (flavor detection)", () => { await sendWebhookNotifications([sub], makeUpdate({ id: 99 })); - const [, init] = fetchMock.calls[0].args; + const [, init] = fetchMock.mock.calls[0]; const body = JSON.parse(init?.body as string); const stringified = init?.body as string; // title stays in a plain_text header block @@ -423,7 +431,7 @@ describe("sendWebhookNotifications (flavor detection)", () => { makeUpdate({ id: 7, status: "maintenance" }), ); - const [, init] = fetchMock.calls[0].args; + const [, init] = fetchMock.mock.calls[0]; const body = JSON.parse(init?.body as string); expect(body.embeds[0].url).toBe( "https://demo.openstatus.dev/events/maintenance/7", @@ -613,8 +621,8 @@ describe("sendTestWebhookRequest", () => { flavor: "generic", }); - assertSpyCalls(fetchMock, 1); - const [url, init] = fetchMock.calls[0].args; + expect(fetchMock).toHaveBeenCalledTimes(1); + const [url, init] = fetchMock.mock.calls[0]; expect(url).toBe("https://example.com/hook"); expect(init?.method).toBe("POST"); const body = JSON.parse(init?.body as string); @@ -639,7 +647,7 @@ describe("sendTestWebhookRequest", () => { headers: { "X-Custom": "abc" }, }); - const [, init] = fetchMock.calls[0].args; + const [, init] = fetchMock.mock.calls[0]; const headers = init?.headers as Record; expect(headers["X-Custom"]).toBe("abc"); }); diff --git a/packages/subscriptions/src/dispatcher.test.ts b/packages/subscriptions/src/dispatcher.test.ts index 8bf065463..25a67b602 100644 --- a/packages/subscriptions/src/dispatcher.test.ts +++ b/packages/subscriptions/src/dispatcher.test.ts @@ -17,16 +17,17 @@ import { createTestWorkspace, } from "@openstatus/db/src/test/factories"; import { EmailClient } from "@openstatus/emails"; -import { expect } from "@std/expect"; import { afterAll, afterEach, beforeAll, beforeEach, describe, + expect, test, -} from "@std/testing/bdd"; -import { assertSpyCalls, type Stub, stub } from "@std/testing/mock"; + vi, + type MockInstance, +} from "vitest"; import { dispatchMaintenanceUpdate, @@ -38,7 +39,7 @@ import type { PageUpdate } from "./types"; // RESEND_API_KEY is set in test-preload.ts (see bunfig.toml) so @openstatus/emails // loads successfully and EmailClient prototype methods can be spied on. -let sendPageUpdateMock: Stub; +let sendPageUpdateMock: MockInstance; let rejectNextSend: Error | null = null; // Built in `beforeAll` — a private page keeps the subscriber set this suite @@ -133,18 +134,20 @@ afterAll(cleanAll); beforeEach(() => { rejectNextSend = null; - sendPageUpdateMock = stub(EmailClient.prototype, "sendPageUpdate", () => { - if (rejectNextSend) { - const error = rejectNextSend; - rejectNextSend = null; - return Promise.reject(error); - } - return Promise.resolve(undefined); - }); + sendPageUpdateMock = vi + .spyOn(EmailClient.prototype, "sendPageUpdate") + .mockImplementation(() => { + if (rejectNextSend) { + const error = rejectNextSend; + rejectNextSend = null; + return Promise.reject(error); + } + return Promise.resolve(undefined); + }); }); afterEach(() => { - sendPageUpdateMock.restore(); + sendPageUpdateMock.mockRestore(); }); // ─── dispatchPageUpdate - component filtering ───────────────────────────────── @@ -155,8 +158,8 @@ describe("dispatchPageUpdate - component filtering", () => { makePageUpdate({ pageComponentIds: [COMPONENT_1] }), ); - assertSpyCalls(sendPageUpdateMock, 1); - const { subscribers } = sendPageUpdateMock.calls[0].args[0]; + expect(sendPageUpdateMock).toHaveBeenCalledTimes(1); + const { subscribers } = sendPageUpdateMock.mock.calls[0][0]; const emails = subscribers.map((s: { email: string }) => s.email); expect(emails).toContain(EMAILS.entirePage); @@ -169,7 +172,7 @@ describe("dispatchPageUpdate - component filtering", () => { makePageUpdate({ pageComponentIds: [COMPONENT_2] }), ); - const { subscribers } = sendPageUpdateMock.calls[0].args[0]; + const { subscribers } = sendPageUpdateMock.mock.calls[0][0]; const emails = subscribers.map((s: { email: string }) => s.email); expect(emails).toContain(EMAILS.entirePage); @@ -182,7 +185,7 @@ describe("dispatchPageUpdate - component filtering", () => { makePageUpdate({ pageComponentIds: [COMPONENT_1, COMPONENT_2] }), ); - const { subscribers } = sendPageUpdateMock.calls[0].args[0]; + const { subscribers } = sendPageUpdateMock.mock.calls[0][0]; const emails = subscribers.map((s: { email: string }) => s.email); expect(emails).toContain(EMAILS.entirePage); @@ -193,8 +196,8 @@ describe("dispatchPageUpdate - component filtering", () => { test("notifies only the entire-page subscriber when update has no affected components", async () => { await dispatchPageUpdate(makePageUpdate({ pageComponentIds: [] })); - assertSpyCalls(sendPageUpdateMock, 1); - const { subscribers } = sendPageUpdateMock.calls[0].args[0]; + expect(sendPageUpdateMock).toHaveBeenCalledTimes(1); + const { subscribers } = sendPageUpdateMock.mock.calls[0][0]; const emails = subscribers.map((s: { email: string }) => s.email); // Assert only against this suite's own subscribers — package test suites @@ -212,7 +215,7 @@ describe("dispatchPageUpdate - component filtering", () => { makePageUpdate({ pageComponentIds: [COMPONENT_1] }), ); - const { subscribers } = sendPageUpdateMock.calls[0].args[0]; + const { subscribers } = sendPageUpdateMock.mock.calls[0][0]; const emails = subscribers.map((s: { email: string }) => s.email); expect(emails).not.toContain(EMAILS.component2); @@ -227,7 +230,7 @@ describe("dispatchPageUpdate - edge cases", () => { makePageUpdate({ pageId: 99999, pageComponentIds: [COMPONENT_1] }), ); - assertSpyCalls(sendPageUpdateMock, 0); + expect(sendPageUpdateMock).toHaveBeenCalledTimes(0); }); test("does not propagate channel failure — resolves even when sendPageUpdate throws", async () => { @@ -277,7 +280,7 @@ describe("dispatchMaintenanceUpdate", () => { await dispatchMaintenanceUpdate(update.id); - const args = sendPageUpdateMock.calls[0].args[0]; + const args = sendPageUpdateMock.mock.calls[0][0]; expect(args.message).toBe("specific update message"); expect(args.date).toBe(occurredAt.toISOString()); expect(args.startsAt).toBe(startsAt.toISOString()); @@ -343,8 +346,8 @@ describe("dispatchStatusReportUpdate - impacts", () => { try { await dispatchStatusReportUpdate(earlier.id); - assertSpyCalls(sendPageUpdateMock, 1); - const args = sendPageUpdateMock.calls[0].args[0]; + expect(sendPageUpdateMock).toHaveBeenCalledTimes(1); + const args = sendPageUpdateMock.mock.calls[0][0]; expect( args.componentImpacts.map((c: { impact: string }) => c.impact), ).toEqual(["major_outage"]); diff --git a/packages/subscriptions/src/test-preload.ts b/packages/subscriptions/src/test-preload.ts index b26048d3c..deccbf2a1 100644 --- a/packages/subscriptions/src/test-preload.ts +++ b/packages/subscriptions/src/test-preload.ts @@ -3,8 +3,7 @@ * before module-level env validations (e.g. @openstatus/emails, @openstatus/db) * fire on import. * - * Bun's test runner set NODE_ENV=test automatically; Deno does not, so we set - * it here. The @openstatus/db env hack maps NODE_ENV=test to the local turso + * The @openstatus/db env hack maps NODE_ENV=test to the local turso * URL, and @openstatus/emails validates RESEND_API_KEY at module load. */ process.env.NODE_ENV = "test"; diff --git a/packages/subscriptions/tsconfig.json b/packages/subscriptions/tsconfig.json index b3f1822a7..4225a2de0 100644 --- a/packages/subscriptions/tsconfig.json +++ b/packages/subscriptions/tsconfig.json @@ -7,15 +7,5 @@ "jsx": "react-jsx" }, "include": [".", "src"], - "exclude": [ - "dist", - "build", - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + "exclude": ["dist", "build", "node_modules"] } diff --git a/packages/subscriptions/vitest.config.ts b/packages/subscriptions/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/subscriptions/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index ee345766c..8a7eb88be 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -2,16 +2,15 @@ "name": "@openstatus/test-utils", "version": "1.0.0", "private": true, - "main": "src/index.ts", "exports": { - ".": "./src/index.ts" + "./vitest": "./src/vitest.ts" }, "scripts": { - "check": "deno check --sloppy-imports ." + "check": "tsc --noEmit" }, "dependencies": { - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19" + "vite": "catalog:", + "vitest": "catalog:" }, "devDependencies": { "@openstatus/tsconfig": "workspace:*", diff --git a/packages/test-utils/src/index.ts b/packages/test-utils/src/index.ts deleted file mode 100644 index 25c2570b3..000000000 --- a/packages/test-utils/src/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Single import surface for migrated tests: `import { ... } from "@openstatus/test-utils"`. -// Importing this module registers the Bun-compat matchers (./matchers side-effect). -import "./matchers.ts"; - -export { - after, - afterAll, - afterEach, - before, - beforeAll, - beforeEach, - describe, - it, - test, -} from "./runner.ts"; -export { expect } from "jsr:@std/expect"; -export { type MockFn, mock } from "./mock-fn.ts"; -export { - assertSpyCall, - assertSpyCalls, - returnsNext, - type Spy, - spy, - type Stub, - stub, -} from "jsr:@std/testing/mock"; -export { FakeTime } from "jsr:@std/testing/time"; diff --git a/packages/test-utils/src/matchers.ts b/packages/test-utils/src/matchers.ts deleted file mode 100644 index 36d58bd32..000000000 --- a/packages/test-utils/src/matchers.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { expect } from "jsr:@std/expect"; - -// @std/expect stopped re-exporting its matcher-context type; these matchers only -// read `value`, so a minimal local shape is enough. -type ExpectMatcherContext = { value: unknown }; - -type MatcherResult = { pass: boolean; message: () => string }; - -const isObject = (v: unknown): v is Record => - typeof v === "object" && v !== null; - -const size = (v: unknown): number => { - if (typeof v === "string" || Array.isArray(v)) return v.length; - if (v instanceof Map || v instanceof Set) return v.size; - if (isObject(v)) return Object.keys(v).length; - return -1; -}; - -// Bun's `expect` extension matchers, reimplemented on @std/expect so the ~150 -// existing call sites keep working under `deno test`. -expect.extend({ - toBeNumber(context: ExpectMatcherContext): MatcherResult { - const pass = - typeof context.value === "number" && !Number.isNaN(context.value); - return { pass, message: () => `expected ${context.value} to be a number` }; - }, - toBeString(context: ExpectMatcherContext): MatcherResult { - return { - pass: typeof context.value === "string", - message: () => `expected ${context.value} to be a string`, - }; - }, - toBeBoolean(context: ExpectMatcherContext): MatcherResult { - return { - pass: typeof context.value === "boolean", - message: () => `expected ${context.value} to be a boolean`, - }; - }, - toBeArray(context: ExpectMatcherContext): MatcherResult { - return { - pass: Array.isArray(context.value), - message: () => `expected ${context.value} to be an array`, - }; - }, - toBeTrue(context: ExpectMatcherContext): MatcherResult { - return { - pass: context.value === true, - message: () => `expected ${context.value} to be true`, - }; - }, - toBeFalse(context: ExpectMatcherContext): MatcherResult { - return { - pass: context.value === false, - message: () => `expected ${context.value} to be false`, - }; - }, - toBeNil(context: ExpectMatcherContext): MatcherResult { - return { - pass: context.value === null || context.value === undefined, - message: () => `expected ${context.value} to be null or undefined`, - }; - }, - toBeEmpty(context: ExpectMatcherContext): MatcherResult { - return { - pass: size(context.value) === 0, - message: () => `expected ${context.value} to be empty`, - }; - }, - toBeOneOf( - context: ExpectMatcherContext, - expected: readonly unknown[], - ): MatcherResult { - return { - pass: expected.includes(context.value), - message: () => - `expected ${context.value} to be one of ${JSON.stringify(expected)}`, - }; - }, - toContainValue(context: ExpectMatcherContext, value: unknown): MatcherResult { - const pass = - isObject(context.value) && Object.values(context.value).includes(value); - return { pass, message: () => `expected object to contain value ${value}` }; - }, - toContainKey(context: ExpectMatcherContext, key: string): MatcherResult { - const pass = - isObject(context.value) && Object.keys(context.value).includes(key); - return { pass, message: () => `expected object to contain key ${key}` }; - }, - pass(_context: ExpectMatcherContext, _message?: string): MatcherResult { - return { pass: true, message: () => _message ?? "passed" }; - }, - fail(_context: ExpectMatcherContext, message?: string): MatcherResult { - return { pass: false, message: () => message ?? "failed" }; - }, -}); diff --git a/packages/test-utils/src/mock-fn.ts b/packages/test-utils/src/mock-fn.ts deleted file mode 100644 index 2d71214fb..000000000 --- a/packages/test-utils/src/mock-fn.ts +++ /dev/null @@ -1,112 +0,0 @@ -// Bun-compatible `mock()` for the Deno migration. Bun's mock function isn't -// available under `deno test`; this reimplements the subset our suites use -// (the jest-style `.mock.calls` + `.mockX()` helpers) on plain closures. -// -// NOTE: bun's `mock.module(specifier, factory)` has no Deno equivalent and is -// intentionally NOT provided here — module replacement is done via the -// `--import-map` flag and test-double modules instead. - -// oxlint-disable-next-line typescript/no-explicit-any -type AnyFn = (...args: any[]) => any; - -interface MockState { - // oxlint-disable-next-line typescript/no-explicit-any - calls: any[][]; - results: { type: "return" | "throw"; value: unknown }[]; -} - -export interface MockFn extends AnyFn { - mock: MockState; - mockClear(): MockFn; - mockReset(): MockFn; - mockImplementation(fn: AnyFn): MockFn; - mockImplementationOnce(fn: AnyFn): MockFn; - mockReturnValue(value: unknown): MockFn; - mockReturnValueOnce(value: unknown): MockFn; - mockResolvedValue(value: unknown): MockFn; - mockResolvedValueOnce(value: unknown): MockFn; - mockRejectedValue(value: unknown): MockFn; - mockRestore(): MockFn; -} - -export function mock(impl?: AnyFn): MockFn { - let defaultImpl = impl; - const onceQueue: AnyFn[] = []; - const state: MockState = { calls: [], results: [] }; - - // oxlint-disable-next-line typescript/no-explicit-any - const fn = ((...args: any[]) => { - state.calls.push(args); - const use = onceQueue.length ? onceQueue.shift() : defaultImpl; - try { - const value = use ? use(...args) : undefined; - state.results.push({ type: "return", value }); - return value; - } catch (err) { - state.results.push({ type: "throw", value: err }); - throw err; - } - }) as MockFn; - - fn.mock = state; - // Expose the @std/expect mock interface (Symbol.for("@MOCK")) so its - // `.toHaveBeenCalled*` matchers recognize this shim, not just `.mock.calls`. - Object.defineProperty(fn, Symbol.for("@MOCK"), { - get() { - return { - calls: state.calls.map((args, i) => { - const r = state.results[i]; - return { - args, - returned: r?.type === "return" ? r.value : undefined, - thrown: r?.type === "throw" ? r.value : undefined, - timestamp: 0, - returns: r?.type === "return", - throws: r?.type === "throw", - }; - }), - }; - }, - }); - fn.mockClear = () => { - state.calls = []; - state.results = []; - return fn; - }; - fn.mockReset = () => { - fn.mockClear(); - onceQueue.length = 0; - defaultImpl = undefined; - return fn; - }; - fn.mockImplementation = (f) => { - defaultImpl = f; - return fn; - }; - fn.mockImplementationOnce = (f) => { - onceQueue.push(f); - return fn; - }; - fn.mockReturnValue = (value) => { - defaultImpl = () => value; - return fn; - }; - fn.mockReturnValueOnce = (value) => { - onceQueue.push(() => value); - return fn; - }; - fn.mockResolvedValue = (value) => { - defaultImpl = () => Promise.resolve(value); - return fn; - }; - fn.mockResolvedValueOnce = (value) => { - onceQueue.push(() => Promise.resolve(value)); - return fn; - }; - fn.mockRejectedValue = (value) => { - defaultImpl = () => Promise.reject(value); - return fn; - }; - fn.mockRestore = () => fn; - return fn; -} diff --git a/packages/test-utils/src/runner.ts b/packages/test-utils/src/runner.ts deleted file mode 100644 index 55dd22f4a..000000000 --- a/packages/test-utils/src/runner.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - describe as nodeDescribe, - it as nodeIt, - test as nodeTest, -} from "node:test"; - -type Body = () => void | Promise; -type BaseFn = (name: string, body: Body) => void; -type EachCase = readonly unknown[]; - -const formatName = (name: string, args: EachCase, index: number): string => { - let i = 0; - const interpolated = name.replace(/%[spdijfo%]/g, (token) => { - if (token === "%%") return "%"; - const arg = args[i++]; - return typeof arg === "string" ? arg : JSON.stringify(arg); - }); - return interpolated === name && args.length > 0 - ? `${name} [${index}]` - : interpolated; -}; - -const makeEach = - (base: BaseFn) => - (cases: readonly T[]) => - (name: string, body: (...args: T) => void | Promise): void => { - cases.forEach((args, index) => { - base(formatName(name, args, index), () => body(...args)); - }); - }; - -// node:test's `test`/`it`/`describe` lack Jest/Bun's `.each`; everything else -// (.skip/.todo/.only) exists natively and is preserved by reference. -type Augmented = F & { - each: ( - cases: readonly T[], - ) => (name: string, body: (...args: T) => void | Promise) => void; -}; - -const augment = (base: F): Augmented => { - const fn = base as Augmented; - fn.each = makeEach(base as unknown as BaseFn); - return fn; -}; - -export const test = augment(nodeTest); -export const it = augment(nodeIt); -export const describe = augment( - nodeDescribe as unknown as typeof nodeTest, -) as unknown as Augmented; -export { - after, - after as afterAll, - afterEach, - before, - before as beforeAll, - beforeEach, -} from "node:test"; diff --git a/packages/test-utils/src/vitest.ts b/packages/test-utils/src/vitest.ts new file mode 100644 index 000000000..c0a0f1611 --- /dev/null +++ b/packages/test-utils/src/vitest.ts @@ -0,0 +1,91 @@ +import { readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { parseEnv } from "node:util"; + +import type { Plugin } from "vite"; +import { defineConfig } from "vitest/config"; + +type Options = { + /** Test-double swaps, in Deno import-map format, relative to the package. */ + importMap?: string; + /** Loaded into `process.env` for every test file, relative to the package. */ + envFile?: string; + include?: string[]; +}; + +/** + * Applies a Deno-style import map: exact bare keys, `/`-suffixed prefix keys, + * and relative keys matched against the importer-resolved path without + * extension probing — so `./page-urls` is swapped but `./page-urls.ts` is not. + */ +export function importMap(file: string): Plugin { + const base = dirname(file); + const { imports } = JSON.parse(readFileSync(file, "utf8")) as { + imports: Record; + }; + const exact = new Map(); + const prefixes: [string, string][] = []; + const paths = new Map(); + for (const [key, value] of Object.entries(imports)) { + const target = resolve(base, value); + if (key.startsWith("./") || key.startsWith("../")) { + paths.set(resolve(base, key), target); + } else if (key.endsWith("/")) { + prefixes.push([key, value.endsWith("/") ? `${target}/` : target]); + } else { + exact.set(key, target); + } + } + + return { + name: "openstatus:import-map", + enforce: "pre", + resolveId(source, importer, options) { + const swapped = + exact.get(source) ?? + (importer && (source.startsWith("./") || source.startsWith("../")) + ? paths.get(resolve(dirname(importer), source)) + : undefined); + if (swapped) { + return this.resolve(swapped, importer, { ...options, skipSelf: true }); + } + for (const [prefix, target] of prefixes) { + if (source.startsWith(prefix)) { + return this.resolve(target + source.slice(prefix.length), importer, { + ...options, + skipSelf: true, + }); + } + } + return null; + }, + }; +} + +export function defineTestConfig(root: string, options: Options = {}) { + // Like `--env-file`: the file never overrides a variable already set (CI + // points DATABASE_URL at its own sqld). + const fileEnv = options.envFile + ? parseEnv(readFileSync(resolve(root, options.envFile), "utf8")) + : {}; + const env = Object.fromEntries( + Object.entries(fileEnv).filter(([key]) => process.env[key] === undefined), + ); + return defineConfig({ + root, + plugins: options.importMap + ? [importMap(resolve(root, options.importMap))] + : [], + resolve: { tsconfigPaths: true }, + oxc: { jsx: { runtime: "automatic", importSource: "react" } }, + test: { + include: options.include ?? ["**/*.test.{ts,tsx}"], + exclude: ["**/node_modules/**", "**/dist/**", "**/.next/**"], + env: env as Record, + // Integration suites talk to a real libSQL server; vitest's 5s default + // is too tight for the larger seeded fixtures. + testTimeout: 30_000, + hookTimeout: 30_000, + }, + }); +} diff --git a/packages/test-utils/tsconfig.json b/packages/test-utils/tsconfig.json new file mode 100644 index 000000000..82f44d0f0 --- /dev/null +++ b/packages/test-utils/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@openstatus/tsconfig/base.json", + "include": ["src"] +} diff --git a/packages/theme-store/package.json b/packages/theme-store/package.json index 5b6b92cc7..c0c57261f 100644 --- a/packages/theme-store/package.json +++ b/packages/theme-store/package.json @@ -8,13 +8,13 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports" + "test": "vitest run" }, "dependencies": {}, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/theme-store/src/__tests__/custom-theme.test.ts b/packages/theme-store/src/__tests__/custom-theme.test.ts index f6a255cb3..aea331c5c 100644 --- a/packages/theme-store/src/__tests__/custom-theme.test.ts +++ b/packages/theme-store/src/__tests__/custom-theme.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { CUSTOM_THEME_VALUE_MAX_LENGTH, diff --git a/packages/theme-store/src/__tests__/theme-io.test.ts b/packages/theme-store/src/__tests__/theme-io.test.ts index cc2033f1b..6d9c183f9 100644 --- a/packages/theme-store/src/__tests__/theme-io.test.ts +++ b/packages/theme-store/src/__tests__/theme-io.test.ts @@ -1,7 +1,6 @@ import { readdirSync, readFileSync } from "node:fs"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { parseThemeInput, diff --git a/packages/theme-store/tsconfig.json b/packages/theme-store/tsconfig.json index 9f7ee457b..aed15c74a 100644 --- a/packages/theme-store/tsconfig.json +++ b/packages/theme-store/tsconfig.json @@ -1,13 +1,4 @@ { "extends": "@openstatus/tsconfig/base.json", - "include": ["src", "*.ts"], - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + "include": ["src", "*.ts"] } diff --git a/packages/theme-store/vitest.config.ts b/packages/theme-store/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/theme-store/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/tinybird/package.json b/packages/tinybird/package.json index 5945d7c6c..34135eaad 100644 --- a/packages/tinybird/package.json +++ b/packages/tinybird/package.json @@ -5,7 +5,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "NODE_ENV=test deno test --parallel -A --no-check --sloppy-imports" + "test": "NODE_ENV=test vitest run" }, "dependencies": { "@chronark/zod-bird": "catalog:", @@ -14,12 +14,12 @@ "devDependencies": { "@libsql/client": "catalog:", "@openstatus/db": "workspace:*", + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", "@openstatus/utils": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", "drizzle-orm": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/tinybird/src/client.test.ts b/packages/tinybird/src/client.test.ts index 17b015f40..6b5a98ef6 100644 --- a/packages/tinybird/src/client.test.ts +++ b/packages/tinybird/src/client.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { noopFlagSchema, OSTinybird } from "./client"; diff --git a/packages/tinybird/src/schema.test.ts b/packages/tinybird/src/schema.test.ts index de42ed20f..965eebf60 100644 --- a/packages/tinybird/src/schema.test.ts +++ b/packages/tinybird/src/schema.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { calculateTiming } from "./schema"; diff --git a/packages/tinybird/tsconfig.json b/packages/tinybird/tsconfig.json index 000ea995d..856c3e555 100644 --- a/packages/tinybird/tsconfig.json +++ b/packages/tinybird/tsconfig.json @@ -1,14 +1,6 @@ { "extends": "@openstatus/tsconfig/base.json", - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ], + "exclude": ["node_modules"], "compilerOptions": { "esModuleInterop": true, "forceConsistentCasingInFileNames": true, diff --git a/packages/tinybird/vitest.config.ts b/packages/tinybird/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/tinybird/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/tracker/package.json b/packages/tracker/package.json index 56ca0b044..003592341 100644 --- a/packages/tracker/package.json +++ b/packages/tracker/package.json @@ -8,7 +8,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports" + "test": "vitest run" }, "dependencies": { "@openstatus/db": "workspace:*", @@ -16,9 +16,9 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/tracker/src/tracker.test.ts b/packages/tracker/src/tracker.test.ts index d2ace251a..0a5897fb7 100644 --- a/packages/tracker/src/tracker.test.ts +++ b/packages/tracker/src/tracker.test.ts @@ -4,8 +4,7 @@ import type { StatusReport, StatusReportUpdate, } from "@openstatus/db/src/schema"; -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { blacklistDates } from "./blacklist"; import { classNames, statusDetails } from "./config"; diff --git a/packages/tracker/src/utils.test.ts b/packages/tracker/src/utils.test.ts index 6615cc76f..0c70142fa 100644 --- a/packages/tracker/src/utils.test.ts +++ b/packages/tracker/src/utils.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { isInBlacklist } from "./blacklist"; import { endOfDay, isSameDay, startOfDay } from "./utils"; diff --git a/packages/tracker/tsconfig.json b/packages/tracker/tsconfig.json index bbdc601ad..bded80f35 100644 --- a/packages/tracker/tsconfig.json +++ b/packages/tracker/tsconfig.json @@ -3,14 +3,5 @@ "include": ["src", "*.ts"], "compilerOptions": { "target": "ES2021" - }, - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + } } diff --git a/packages/tracker/vitest.config.ts b/packages/tracker/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/tracker/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/upstash/package.json b/packages/upstash/package.json index 1622f7587..36e67ead6 100644 --- a/packages/upstash/package.json +++ b/packages/upstash/package.json @@ -5,18 +5,18 @@ "main": "./src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports" + "test": "vitest run" }, "dependencies": { "@upstash/qstash": "catalog:", "@upstash/redis": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", "@types/node": "catalog:", "tsup": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/upstash/src/redis/incr-with-ttl.test.ts b/packages/upstash/src/redis/incr-with-ttl.test.ts index 17befdcd5..a262e64c6 100644 --- a/packages/upstash/src/redis/incr-with-ttl.test.ts +++ b/packages/upstash/src/redis/incr-with-ttl.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { incrWithTtl } from "./incr-with-ttl"; diff --git a/packages/upstash/tsconfig.json b/packages/upstash/tsconfig.json index 000ea995d..856c3e555 100644 --- a/packages/upstash/tsconfig.json +++ b/packages/upstash/tsconfig.json @@ -1,14 +1,6 @@ { "extends": "@openstatus/tsconfig/base.json", - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ], + "exclude": ["node_modules"], "compilerOptions": { "esModuleInterop": true, "forceConsistentCasingInFileNames": true, diff --git a/packages/upstash/vitest.config.ts b/packages/upstash/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/upstash/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/packages/utils/package.json b/packages/utils/package.json index c5bc3bf82..aecda2c8a 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -8,7 +8,7 @@ "main": "src/index.ts", "scripts": { "check": "tsc --noEmit", - "test": "deno test --parallel -A --no-check --sloppy-imports" + "test": "vitest run" }, "dependencies": { "@openstatus/assertions": "workspace:*", @@ -16,9 +16,9 @@ "zod": "catalog:" }, "devDependencies": { + "@openstatus/test-utils": "workspace:*", "@openstatus/tsconfig": "workspace:*", - "@std/expect": "jsr:^1.0.19", - "@std/testing": "jsr:^1.0.19", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/packages/utils/src/curl.test.ts b/packages/utils/src/curl.test.ts index c64c4fc88..bde04a48d 100644 --- a/packages/utils/src/curl.test.ts +++ b/packages/utils/src/curl.test.ts @@ -1,8 +1,42 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { spawn } from "node:child_process"; +import { createServer } from "node:http"; +import type { AddressInfo } from "node:net"; + +import { describe, expect, it } from "vitest"; import { buildCurlCommand } from "./curl"; +async function serve(onBody: (body: Buffer) => Buffer | string) { + const server = createServer((req, res) => { + const chunks: Buffer[] = []; + req.on("data", (chunk: Buffer) => chunks.push(chunk)); + req.on("end", () => res.end(onBody(Buffer.concat(chunks)))); + }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + return { + port: (server.address() as AddressInfo).port, + close: () => new Promise((resolve) => server.close(() => resolve())), + }; +} + +// Async on purpose: the server above runs in this process, so a sync spawn +// would block the request it is waiting on. +function sh(command: string) { + return new Promise<{ code: number | null; stdout: Uint8Array }>( + (resolve, reject) => { + const child = spawn("sh", ["-c", command], { + env: { ...process.env, NO_PROXY: "*" }, + }); + const chunks: Buffer[] = []; + child.stdout.on("data", (chunk: Buffer) => chunks.push(chunk)); + child.on("error", reject); + child.on("close", (code) => + resolve({ code, stdout: new Uint8Array(Buffer.concat(chunks)) }), + ); + }, + ); +} + describe("buildCurlCommand", () => { it("renders a plain GET without an explicit method", () => { expect(buildCurlCommand({ url: "https://example.com" })).toBe( @@ -31,10 +65,7 @@ describe("buildCurlCommand", () => { }); it("sends decoded binary POST bytes without shell or text conversion", async () => { - const server = Deno.serve( - { hostname: "127.0.0.1", port: 0 }, - async (request) => new Response(await request.arrayBuffer()), - ); + const server = await serve((body) => body); try { for (const { body, bytes } of [ { @@ -48,31 +79,26 @@ describe("buildCurlCommand", () => { }, ]) { const command = buildCurlCommand({ - url: `http://127.0.0.1:${server.addr.port}/upload`, + url: `http://127.0.0.1:${server.port}/upload`, method: "POST", body, headers: [{ key: "Content-Type", value: "application/octet-stream" }], timeout: 5000, }); - const result = await new Deno.Command("sh", { - args: ["-c", command], - env: { NO_PROXY: "*" }, - stdout: "piped", - stderr: "piped", - }).output(); + const result = await sh(command); expect(result.code).toBe(0); expect(result.stdout).toEqual(new Uint8Array(bytes)); } } finally { - await server.shutdown(); + await server.close(); } }); it("rejects malformed binary bodies before sending a request", async () => { let requests = 0; - const server = Deno.serve({ hostname: "127.0.0.1", port: 0 }, () => { + const server = await serve(() => { requests++; - return new Response("received"); + return "received"; }); try { for (const body of [ @@ -84,23 +110,18 @@ describe("buildCurlCommand", () => { "data:application/octet-stream;base64,aGVsbG8=\u2028", ]) { const command = buildCurlCommand({ - url: `http://127.0.0.1:${server.addr.port}/upload`, + url: `http://127.0.0.1:${server.port}/upload`, method: "POST", body, headers: [{ key: "Content-Type", value: "application/octet-stream" }], timeout: 5000, }); - const result = await new Deno.Command("sh", { - args: ["-c", command], - env: { NO_PROXY: "*" }, - stdout: "piped", - stderr: "piped", - }).output(); + const result = await sh(command); expect(requests).toBe(0); expect(result.code).not.toBe(0); } } finally { - await server.shutdown(); + await server.close(); } }); diff --git a/packages/utils/src/headers.test.ts b/packages/utils/src/headers.test.ts index 61c746acd..f41247147 100644 --- a/packages/utils/src/headers.test.ts +++ b/packages/utils/src/headers.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, test } from "@std/testing/bdd"; +import { describe, expect, test } from "vitest"; import { headerPairSchema } from "./headers"; diff --git a/packages/utils/src/payloads.test.ts b/packages/utils/src/payloads.test.ts index 13a5fb124..3310d1df7 100644 --- a/packages/utils/src/payloads.test.ts +++ b/packages/utils/src/payloads.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { DNSPayloadSchema, diff --git a/packages/utils/src/person-name.test.ts b/packages/utils/src/person-name.test.ts index fed7a4402..bfa1b2117 100644 --- a/packages/utils/src/person-name.test.ts +++ b/packages/utils/src/person-name.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { personName } from "./person-name"; diff --git a/packages/utils/src/ssrf.test.ts b/packages/utils/src/ssrf.test.ts index 587e3de8e..8d5a3083c 100644 --- a/packages/utils/src/ssrf.test.ts +++ b/packages/utils/src/ssrf.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { assertSafeUrl, diff --git a/packages/utils/src/status.test.ts b/packages/utils/src/status.test.ts index 7e1eae0b9..8949ae5bb 100644 --- a/packages/utils/src/status.test.ts +++ b/packages/utils/src/status.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { type PageUpdateStatus, statusLabel } from "./status"; diff --git a/packages/utils/src/stream.test.ts b/packages/utils/src/stream.test.ts index 687964064..e46cdac99 100644 --- a/packages/utils/src/stream.test.ts +++ b/packages/utils/src/stream.test.ts @@ -1,5 +1,4 @@ -import { expect } from "@std/expect"; -import { describe, it } from "@std/testing/bdd"; +import { describe, expect, it } from "vitest"; import { iteratorToStream, yieldMany } from "./stream"; diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json index 9f7ee457b..aed15c74a 100644 --- a/packages/utils/tsconfig.json +++ b/packages/utils/tsconfig.json @@ -1,13 +1,4 @@ { "extends": "@openstatus/tsconfig/base.json", - "include": ["src", "*.ts"], - "exclude": [ - "node_modules", - "**/*.test.ts", - "**/*.test.tsx", - "**/__tests__/**", - "**/test/**", - "**/*.mock.ts", - "**/test-preload.ts" - ] + "include": ["src", "*.ts"] } diff --git a/packages/utils/vitest.config.ts b/packages/utils/vitest.config.ts new file mode 100644 index 000000000..26bdb37da --- /dev/null +++ b/packages/utils/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineTestConfig } from "@openstatus/test-utils/vitest"; + +export default defineTestConfig(import.meta.dirname); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6effe7780..345c64df5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -659,6 +659,12 @@ catalogs: vaul: specifier: 1.1.2 version: 1.1.2 + vite: + specifier: 8.3.4 + version: 8.3.4 + vitest: + specifier: 5.0.3 + version: 5.0.3 zod: specifier: 4.6.5 version: 4.6.5 @@ -974,12 +980,9 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../packages/test-utils '@tailwindcss/postcss': specifier: 'catalog:' version: 4.3.3 @@ -1010,6 +1013,9 @@ importers: typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) apps/screenshot-service: dependencies: @@ -1252,12 +1258,6 @@ importers: '@oxc-node/core': specifier: 'catalog:' version: 0.1.5 - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@std/yaml': specifier: jsr:^1.0.9 version: '@jsr/std__yaml@1.3.0' @@ -1273,6 +1273,9 @@ importers: typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) apps/status-page: dependencies: @@ -1481,15 +1484,12 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../packages/test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../../packages/tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@tailwindcss/postcss': specifier: 'catalog:' version: 4.3.3 @@ -1520,6 +1520,9 @@ importers: typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) apps/web: dependencies: @@ -1767,15 +1770,12 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../packages/test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../../packages/tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@tailwindcss/postcss': specifier: 'catalog:' version: 4.3.3 @@ -1809,6 +1809,9 @@ importers: unified: specifier: 'catalog:' version: 11.0.5 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) apps/workflows: dependencies: @@ -1963,6 +1966,9 @@ importers: typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/ai: dependencies: @@ -1976,21 +1982,21 @@ importers: '@openstatus/db': specifier: workspace:* version: link:../db + '@openstatus/test-utils': + specifier: workspace:* + version: link:../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/analytics: dependencies: @@ -2161,15 +2167,12 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/react': specifier: 'catalog:' version: 19.3.0 @@ -2179,6 +2182,9 @@ importers: typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/assertions: dependencies: @@ -2189,18 +2195,18 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/db: dependencies: @@ -2244,12 +2250,6 @@ importers: '@openstatus/tsconfig': specifier: workspace:* version: link:../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/bcryptjs': specifier: 'catalog:' version: 3.0.0 @@ -2265,6 +2265,9 @@ importers: typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/emails: dependencies: @@ -2293,18 +2296,15 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../tsconfig '@react-email/ui': specifier: 'catalog:' version: 6.9.5(@babel/core@7.29.7(supports-color@8.1.1))(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(babel-plugin-macros@3.1.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 @@ -2314,6 +2314,9 @@ importers: typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/error: dependencies: @@ -2330,18 +2333,18 @@ importers: packages/header-analysis: devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/icons: dependencies: @@ -2374,18 +2377,18 @@ importers: '@openstatus/regions': specifier: workspace:* version: link:../regions + '@openstatus/test-utils': + specifier: workspace:* + version: link:../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/locales: dependencies: @@ -2409,21 +2412,21 @@ importers: specifier: workspace:* version: link:../../regions devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/notifications/bird-whatsapp: dependencies: @@ -2440,21 +2443,21 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/notifications/discord: dependencies: @@ -2471,21 +2474,21 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/notifications/email: dependencies: @@ -2535,6 +2538,9 @@ importers: typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/notifications/google-chat: dependencies: @@ -2551,21 +2557,21 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/notifications/grafana-oncall: dependencies: @@ -2582,21 +2588,21 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/notifications/ms-teams: dependencies: @@ -2613,21 +2619,21 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/notifications/ntfy: dependencies: @@ -2644,21 +2650,21 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/notifications/opsgenie: dependencies: @@ -2681,15 +2687,12 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 @@ -2702,6 +2705,9 @@ importers: typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/notifications/pagerduty: dependencies: @@ -2724,15 +2730,12 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 @@ -2745,6 +2748,9 @@ importers: typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/notifications/slack: dependencies: @@ -2761,21 +2767,21 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/notifications/telegram: dependencies: @@ -2789,21 +2795,21 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/notifications/twillio-sms: dependencies: @@ -2823,15 +2829,12 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 @@ -2841,6 +2844,9 @@ importers: typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/notifications/webhook: dependencies: @@ -2857,21 +2863,21 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/proto: dependencies: @@ -2981,18 +2987,18 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/status-fetcher: dependencies: @@ -3006,15 +3012,15 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' + '@openstatus/test-utils': + specifier: workspace:* + version: link:../test-utils typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/subscriptions: dependencies: @@ -3043,27 +3049,27 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/test-utils: dependencies: - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' + vite: + specifier: 'catalog:' + version: 8.3.4(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1) + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) devDependencies: '@openstatus/tsconfig': specifier: workspace:* @@ -3074,18 +3080,18 @@ importers: packages/theme-store: devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/tinybird: dependencies: @@ -3102,18 +3108,15 @@ importers: '@openstatus/db': specifier: workspace:* version: link:../db + '@openstatus/test-utils': + specifier: workspace:* + version: link:../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../tsconfig '@openstatus/utils': specifier: workspace:* version: link:../utils - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 @@ -3123,6 +3126,9 @@ importers: typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/tracker: dependencies: @@ -3136,18 +3142,18 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/tsconfig: {} @@ -3350,15 +3356,12 @@ importers: specifier: 'catalog:' version: 1.39.0 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' '@types/node': specifier: 'catalog:' version: 26.6.2 @@ -3368,6 +3371,9 @@ importers: typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages/utils: dependencies: @@ -3381,18 +3387,18 @@ importers: specifier: 'catalog:' version: 4.6.5 devDependencies: + '@openstatus/test-utils': + specifier: workspace:* + version: link:../test-utils '@openstatus/tsconfig': specifier: workspace:* version: link:../tsconfig - '@std/expect': - specifier: jsr:^1.0.19 - version: '@jsr/std__expect@1.0.20' - '@std/testing': - specifier: jsr:^1.0.19 - version: '@jsr/std__testing@1.0.20' typescript: specifier: 'catalog:' version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) packages: @@ -5266,30 +5272,6 @@ packages: peerDependencies: jsep: ^0.4.0||^1.0.0 - '@jsr/std__assert@1.0.19': - resolution: {integrity: sha512-pEj6RPkGbqlgRmyKwATp4cUs6+ijxtdrv3bq8v1d2I2CEcMEyPaO8cVKro61wGRDH4cNg8Zx6haztvK/9m7gkA==, tarball: https://npm.jsr.io/~/11/@jsr/std__assert/1.0.19.tgz} - - '@jsr/std__async@1.5.0': - resolution: {integrity: sha512-I2Qekl1oQYM+dpI2RQUCKi1TBuaA7Od3LNzxcqls9s1e2ytiZyREL4403qHJ6UbLNAwbqO2ZhdWAWDUQAajyiA==, tarball: https://npm.jsr.io/~/11/@jsr/std__async/1.5.0.tgz} - - '@jsr/std__data-structures@1.1.2': - resolution: {integrity: sha512-YaNAaUYGr3qBnq8LJ0/FSW6tsnDBMX7hcPuGU7Zi/+aWlAJsZ4OroBNSpRIflizbOvCJCycAnXe9yLjIwDF2KA==, tarball: https://npm.jsr.io/~/11/@jsr/std__data-structures/1.1.2.tgz} - - '@jsr/std__expect@1.0.20': - resolution: {integrity: sha512-AkDNshk9qIKP7OzRcSZ9qb4txPJVOxd2mDWl0XtU19eZecIzcNrxrG9gnz1j5t+U+vm6AEHkjMdwnvIfFQsJ7g==, tarball: https://npm.jsr.io/~/11/@jsr/std__expect/1.0.20.tgz} - - '@jsr/std__fs@1.0.24': - resolution: {integrity: sha512-UunW2Rg++9sA74NJnYMGTsMVQgoFRAwPP02wRRohL0PQBNorbyxHiyCMRaxYQCtLepTLkC4nUyMtcwrGKqTR6g==, tarball: https://npm.jsr.io/~/11/@jsr/std__fs/1.0.24.tgz} - - '@jsr/std__internal@1.0.14': - resolution: {integrity: sha512-JT8b/t40WcR9q0GDwRZUooY7aXeXnFal8iidE/5TckdAFtvpVAsaJ71/Xgf1SfoChs41s6CZkuCYM8toaNgdvA==, tarball: https://npm.jsr.io/~/11/@jsr/std__internal/1.0.14.tgz} - - '@jsr/std__path@1.1.6': - resolution: {integrity: sha512-H1Cmg6z8jFyIsQbVV+vZB4eOx4k6Qg8lyUM6l7nZysj5MZumg89kb0M1bsPBTGQis3fw6mJFkJELo5+AK6oCFA==, tarball: https://npm.jsr.io/~/11/@jsr/std__path/1.1.6.tgz} - - '@jsr/std__testing@1.0.20': - resolution: {integrity: sha512-DRCuU1u4OXD/AXBJ2q9IvMHthMSti1V1604b2+xa5WaAcm6Ghazk0ac1AsMwiAOXEpK0nfIXd1MD3ttT8LQ5Rw==, tarball: https://npm.jsr.io/~/11/@jsr/std__testing/1.0.20.tgz} - '@jsr/std__yaml@1.3.0': resolution: {integrity: sha512-TuFbXlSy3nRuQuR8co2t7yzhv53ObdYZZidn8M958Glj6vBp5b7nxg7wTN8VTyCwC3vpbr6Mfdx9auNe/BMAVA==, tarball: https://npm.jsr.io/~/11/@jsr/std__yaml/1.3.0.tgz} @@ -8190,6 +8172,9 @@ packages: resolution: {integrity: sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg==} deprecated: This is a stub types definition. bcryptjs provides its own type definitions, so you do not need this installed. + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/cors@2.8.19': resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} @@ -8226,6 +8211,9 @@ packages: '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/dom-speech-recognition@0.0.12': resolution: {integrity: sha512-SmLovKV3e/J71U5CBmKYe03Q75biuw7jiEWGoO1arc47CjtmxCr+W7cPJcAAwySSJFwr+jWkr/fPVuVuL+D6Dw==} @@ -8465,6 +8453,20 @@ packages: resolution: {integrity: sha512-TcbdH3YtGS2KU98T99dN4sbf+E4vbJZsJTRN97wKQ7QWQ0BR7U02QX3KN4+5GHxBtKTj1c5CnvTTitc2ZRad0A==} engines: {node: '>= 20'} + '@vitest/mocker@5.0.3': + resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} + '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -8774,6 +8776,10 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types@0.16.3: resolution: {integrity: sha512-FvWoWYfSCM6kRxCSH+MGLHIKKGRL6A6AW7Zek2O32REPQRdg131428uRTKMBYAeRd3XXAaHDS60Wpri7CdKDrA==} engines: {node: '>=4'} @@ -8901,6 +8907,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} + engines: {node: '>=18'} + chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -9623,6 +9633,10 @@ packages: resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} engines: {node: ^18.19.0 || >=20.5.0} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + express-rate-limit@8.7.0: resolution: {integrity: sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==} engines: {node: '>= 16'} @@ -10267,30 +10281,60 @@ packages: cpu: [arm64] os: [android] + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + lightningcss-darwin-arm64@1.32.0: resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + lightningcss-darwin-x64@1.32.0: resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + lightningcss-freebsd-x64@1.32.0: resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + lightningcss-linux-arm-gnueabihf@1.32.0: resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + lightningcss-linux-arm64-gnu@1.32.0: resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} @@ -10298,6 +10342,13 @@ packages: os: [linux] libc: [glibc] + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} @@ -10305,6 +10356,13 @@ packages: os: [linux] libc: [musl] + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} @@ -10312,6 +10370,13 @@ packages: os: [linux] libc: [glibc] + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} @@ -10319,22 +10384,45 @@ packages: os: [linux] libc: [musl] + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + lightningcss-win32-x64-msvc@1.32.0: resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + lightningcss@1.32.0: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -10401,6 +10489,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@1.4.3: + resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==} + mailchecker@6.0.21: resolution: {integrity: sha512-Zd2Lgj4RMnC0IBbbSjnF76Wbd+kkgKMRONgV9yOMKJhj6hcKIAQtj9MrmiGnT2Db+Dy6MsMQoG2HEhPk2YPuGA==} engines: {node: '>=0.10'} @@ -11225,6 +11316,10 @@ packages: resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.29: + resolution: {integrity: sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==} + engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -11796,6 +11891,10 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -11831,6 +11930,9 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} + stdin-discarder@0.2.2: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} engines: {node: '>=18'} @@ -12003,6 +12105,10 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@6.2.1: + resolution: {integrity: sha512-IAbQaPJIIdhVxi0mqy9lez1wEwje0UQf9F1vwtbelEG5Nbgn0nPOxCvU+mNeNS9gyJTAhrHp74CbUaSynQpZcQ==} + engines: {node: '>=20.0.0'} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -12333,6 +12439,90 @@ packages: victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} + vite@8.3.4: + resolution: {integrity: sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.7.1 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@5.0.3: + resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-preview': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.3 + '@vitest/coverage-v8': 5.0.3 + '@vitest/ui': 5.0.3 + happy-dom: '*' + jsdom: '*' + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + w3c-xmlserializer@6.0.0: resolution: {integrity: sha512-4Nsy8K5Tr6SPDH9jhKJOHf7ChDrc1zufZTVSF7x72hwuEXBqxqk9G6cK+K2NRUtB3iELRJqjXb4JPDMBjMTl2Q==} engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} @@ -12398,6 +12588,11 @@ packages: engines: {node: ^16.13.0 || >=18.0.0} hasBin: true + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} + hasBin: true + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -14079,44 +14274,6 @@ snapshots: dependencies: jsep: 1.4.0 - '@jsr/std__assert@1.0.19': - dependencies: - '@jsr/std__internal': 1.0.14 - - '@jsr/std__async@1.5.0': - dependencies: - '@jsr/std__data-structures': 1.1.2 - - '@jsr/std__data-structures@1.1.2': - dependencies: - '@jsr/std__assert': 1.0.19 - - '@jsr/std__expect@1.0.20': - dependencies: - '@jsr/std__assert': 1.0.19 - '@jsr/std__internal': 1.0.14 - '@jsr/std__path': 1.1.6 - - '@jsr/std__fs@1.0.24': - dependencies: - '@jsr/std__internal': 1.0.14 - '@jsr/std__path': 1.1.6 - - '@jsr/std__internal@1.0.14': {} - - '@jsr/std__path@1.1.6': - dependencies: - '@jsr/std__internal': 1.0.14 - - '@jsr/std__testing@1.0.20': - dependencies: - '@jsr/std__assert': 1.0.19 - '@jsr/std__async': 1.5.0 - '@jsr/std__data-structures': 1.1.2 - '@jsr/std__fs': 1.0.24 - '@jsr/std__internal': 1.0.14 - '@jsr/std__path': 1.1.6 - '@jsr/std__yaml@1.3.0': {} '@kobalte/core@0.13.14(solid-js@1.9.15)': @@ -16700,6 +16857,11 @@ snapshots: dependencies: bcryptjs: 3.0.3 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/cors@2.8.19': dependencies: '@types/node': 26.6.2 @@ -16734,6 +16896,8 @@ snapshots: dependencies: '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} + '@types/dom-speech-recognition@0.0.12': {} '@types/estree-jsx@1.0.5': @@ -16926,6 +17090,18 @@ snapshots: '@vercel/cli-exec': 1.0.1 jose: 5.10.0 + '@vitest/mocker@5.0.3(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + msw: 2.15.0(@types/node@26.6.2)(typescript@7.0.2) + vite: 8.3.4(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1) + + '@vitest/spy@5.0.3': {} + '@webassemblyjs/ast@1.14.1': dependencies: '@webassemblyjs/helper-numbers': 1.13.2 @@ -17161,6 +17337,8 @@ snapshots: dependencies: tslib: 2.8.1 + assertion-error@2.0.1: {} + ast-types@0.16.3: dependencies: tslib: 2.8.1 @@ -17278,6 +17456,8 @@ snapshots: ccount@2.0.1: {} + chai@6.3.0: {} + chalk@5.6.2: {} character-entities-html4@2.1.0: {} @@ -17987,6 +18167,8 @@ snapshots: strip-final-newline: 4.0.0 yoctocolors: 2.2.0 + expect-type@1.4.0: {} + express-rate-limit@8.7.0(express@5.2.1(supports-color@8.1.1))(supports-color@8.1.1): dependencies: debug: 4.4.3(supports-color@8.1.1) @@ -18721,36 +18903,69 @@ snapshots: lightningcss-android-arm64@1.32.0: optional: true + lightningcss-android-arm64@1.33.0: + optional: true + lightningcss-darwin-arm64@1.32.0: optional: true + lightningcss-darwin-arm64@1.33.0: + optional: true + lightningcss-darwin-x64@1.32.0: optional: true + lightningcss-darwin-x64@1.33.0: + optional: true + lightningcss-freebsd-x64@1.32.0: optional: true + lightningcss-freebsd-x64@1.33.0: + optional: true + lightningcss-linux-arm-gnueabihf@1.32.0: optional: true + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + lightningcss-linux-arm64-gnu@1.32.0: optional: true + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + lightningcss-linux-arm64-musl@1.32.0: optional: true + lightningcss-linux-arm64-musl@1.33.0: + optional: true + lightningcss-linux-x64-gnu@1.32.0: optional: true + lightningcss-linux-x64-gnu@1.33.0: + optional: true + lightningcss-linux-x64-musl@1.32.0: optional: true + lightningcss-linux-x64-musl@1.33.0: + optional: true + lightningcss-win32-arm64-msvc@1.32.0: optional: true + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + lightningcss-win32-x64-msvc@1.32.0: optional: true + lightningcss-win32-x64-msvc@1.33.0: + optional: true + lightningcss@1.32.0: dependencies: detect-libc: 2.1.2 @@ -18767,6 +18982,22 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + lilconfig@3.1.3: {} limiter@4.1.0: {} @@ -18824,6 +19055,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 + magic-string@1.4.3: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + mailchecker@6.0.21: {} markdown-extensions@2.0.0: {} @@ -19903,6 +20138,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.29: + dependencies: + nanoid: 3.3.19 + picocolors: 1.1.1 + source-map-js: 1.2.2 + postgres-array@2.0.0: optional: true @@ -20752,6 +20993,8 @@ snapshots: source-map-js@1.2.1: {} + source-map-js@1.2.2: {} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -20780,6 +21023,8 @@ snapshots: statuses@2.0.2: {} + std-env@4.3.0: {} + stdin-discarder@0.2.2: {} stream-events@1.0.5: @@ -20935,6 +21180,8 @@ snapshots: tiny-invariant@1.3.3: {} + tinybench@6.2.1: {} + tinyexec@0.3.2: {} tinyexec@1.3.1: {} @@ -21309,6 +21556,45 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 + vite@8.3.4(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.29 + rolldown: 1.2.13 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 26.6.2 + esbuild: 0.28.2 + fsevents: 2.3.3 + jiti: 2.7.0 + terser: 5.51.2 + tsx: 4.23.15 + yaml: 2.9.1 + + vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@26.6.2)(jsdom@30.1.1)(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(msw@2.15.0(@types/node@26.6.2)(typescript@7.0.2))(vite@8.3.4(@types/node@26.6.2)) + chai: 6.3.0 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 2.2.1 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.1 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 8.3.4(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1) + why-is-node-running: 3.2.1 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@types/node': 26.6.2 + jsdom: 30.1.1 + transitivePeerDependencies: + - msw + w3c-xmlserializer@6.0.0: dependencies: xml-name-validator: 5.0.0 @@ -21434,6 +21720,8 @@ snapshots: dependencies: isexe: 3.1.5 + why-is-node-running@3.2.1: {} + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d51813724..9290e3846 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -220,4 +220,6 @@ catalog: unified: 11.0.5 validator: 13.15.35 vaul: 1.1.2 + vite: 8.3.4 + vitest: 5.0.3 zod: 4.6.5 -- 2.51.2