Fix all linter errors

- Remove unused biome suppression comment in completions.ts
- Remove unnecessary if condition in execute-bootstrap.test.ts
- Add eslint-disable comments for any type assertions in client.test.ts
- Add eslint-disable comments for expect().rejects patterns
- Fix template literal number expression with toString()
- Fix error handling in test-db.ts to avoid object stringify

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
igm
2026-01-12 17:30:00 +08:00
parent b78064caeb
commit 665092464a
21 changed files with 47 additions and 34 deletions

View File

@@ -29,6 +29,7 @@ import type { Kysely } from "kysely";
import type { APIContext } from "../../context.js"; import type { APIContext } from "../../context.js";
import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { call } from "@orpc/server"; import { call } from "@orpc/server";
import { createLoggingEmailClient } from "@reviq/emails";
import { import {
createTestUser, createTestUser,
describeE2E, describeE2E,
@@ -39,7 +40,6 @@ import {
uniqueTestId, uniqueTestId,
withTestTransaction, withTestTransaction,
} from "@reviq/test-helpers"; } from "@reviq/test-helpers";
import { createLoggingEmailClient } from "@reviq/emails";
import { router } from "../../router.js"; import { router } from "../../router.js";
import { COOKIE_NAMES } from "../../utils/cookies.js"; import { COOKIE_NAMES } from "../../utils/cookies.js";
import { hashToken } from "../../utils/crypto.js"; import { hashToken } from "../../utils/crypto.js";

View File

@@ -41,6 +41,7 @@ import type { Kysely } from "kysely";
import type { APIContext } from "../../context.js"; import type { APIContext } from "../../context.js";
import { beforeAll, describe, expect, test } from "bun:test"; import { beforeAll, describe, expect, test } from "bun:test";
import { call } from "@orpc/server"; import { call } from "@orpc/server";
import { createLoggingEmailClient } from "@reviq/emails";
import { import {
createTestUser, createTestUser,
describeE2E, describeE2E,
@@ -50,7 +51,6 @@ import {
uniqueTestId, uniqueTestId,
withTestTransaction, withTestTransaction,
} from "@reviq/test-helpers"; } from "@reviq/test-helpers";
import { createLoggingEmailClient } from "@reviq/emails";
import { VirtualAuthenticator } from "@reviq/virtual-authenticator"; import { VirtualAuthenticator } from "@reviq/virtual-authenticator";
import { router } from "../../router.js"; import { router } from "../../router.js";
import { COOKIE_NAMES } from "../../utils/cookies.js"; import { COOKIE_NAMES } from "../../utils/cookies.js";

View File

@@ -23,6 +23,7 @@ import type { Kysely } from "kysely";
import type { APIContext } from "../../context.js"; import type { APIContext } from "../../context.js";
import { beforeAll, describe, expect, test } from "bun:test"; import { beforeAll, describe, expect, test } from "bun:test";
import { call } from "@orpc/server"; import { call } from "@orpc/server";
import { createLoggingEmailClient } from "@reviq/emails";
import { import {
createTestUser, createTestUser,
describeE2E, describeE2E,
@@ -32,7 +33,6 @@ import {
uniqueTestId, uniqueTestId,
withTestTransaction, withTestTransaction,
} from "@reviq/test-helpers"; } from "@reviq/test-helpers";
import { createLoggingEmailClient } from "@reviq/emails";
import { router } from "../../router.js"; import { router } from "../../router.js";
import { COOKIE_NAMES } from "../../utils/cookies.js"; import { COOKIE_NAMES } from "../../utils/cookies.js";
import { hashToken } from "../../utils/crypto.js"; import { hashToken } from "../../utils/crypto.js";

View File

@@ -14,6 +14,7 @@ import type { Kysely } from "kysely";
import type { APIContext } from "../../context.js"; import type { APIContext } from "../../context.js";
import { beforeAll, describe, expect, test } from "bun:test"; import { beforeAll, describe, expect, test } from "bun:test";
import { call } from "@orpc/server"; import { call } from "@orpc/server";
import { createLoggingEmailClient } from "@reviq/emails";
import { import {
createTestUser, createTestUser,
describeE2E, describeE2E,
@@ -23,7 +24,6 @@ import {
uniqueTestId, uniqueTestId,
withTestTransaction, withTestTransaction,
} from "@reviq/test-helpers"; } from "@reviq/test-helpers";
import { createLoggingEmailClient } from "@reviq/emails";
import { router } from "../../router.js"; import { router } from "../../router.js";
import { COOKIE_NAMES } from "../../utils/cookies.js"; import { COOKIE_NAMES } from "../../utils/cookies.js";
import { hashToken } from "../../utils/crypto.js"; import { hashToken } from "../../utils/crypto.js";

View File

@@ -12,6 +12,7 @@ import type { Kysely } from "kysely";
import type { APIContext } from "../../context.js"; import type { APIContext } from "../../context.js";
import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { call } from "@orpc/server"; import { call } from "@orpc/server";
import { createLoggingEmailClient } from "@reviq/emails";
import { import {
createTestUser, createTestUser,
describeE2E, describeE2E,
@@ -23,7 +24,6 @@ import {
uniqueTestId, uniqueTestId,
withTestTransaction, withTestTransaction,
} from "@reviq/test-helpers"; } from "@reviq/test-helpers";
import { createLoggingEmailClient } from "@reviq/emails";
import { VirtualAuthenticator } from "@reviq/virtual-authenticator"; import { VirtualAuthenticator } from "@reviq/virtual-authenticator";
import { router } from "../../router.js"; import { router } from "../../router.js";
import { COOKIE_NAMES } from "../../utils/cookies.js"; import { COOKIE_NAMES } from "../../utils/cookies.js";

View File

@@ -122,7 +122,8 @@ export const loginIfRequestIsCompleted =
.execute(); .execute();
return { session: newSession, deviceTrusted: trusted }; return { session: newSession, deviceTrusted: trusted };
}); },
);
// Set session cookie // Set session cookie
setCookie( setCookie(

View File

@@ -20,7 +20,9 @@ function formatRelativeTime(date: Date): string {
if (diffDays === 0) { if (diffDays === 0) {
const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
return diffHours <= 0 ? "expired" : `in ${diffHours.toLocaleString()} hours`; return diffHours <= 0
? "expired"
: `in ${diffHours.toLocaleString()} hours`;
} }
if (diffDays === 1) { if (diffDays === 1) {

View File

@@ -44,7 +44,6 @@ function completions(
_flags: Record<string, never>, _flags: Record<string, never>,
shell: Shell, shell: Shell,
): void { ): void {
// biome-ignore lint/nursery/noUnnecessaryConditions: switch on union type is valid
switch (shell) { switch (shell) {
case "bash": case "bash":
console.log("To enable bash completions for reviq, run:\n"); console.log("To enable bash completions for reviq, run:\n");

View File

@@ -2,5 +2,8 @@ export { default as AdUnitTable } from "./ad-unit-table.svelte";
export { default as CountryTable } from "./country-table.svelte"; export { default as CountryTable } from "./country-table.svelte";
export { default as DomainTable } from "./domain-table.svelte"; export { default as DomainTable } from "./domain-table.svelte";
export { default as KeyValueTable } from "./key-value-table.svelte"; export { default as KeyValueTable } from "./key-value-table.svelte";
export { default as MetricsTable, type MetricsRow } from "./metrics-table.svelte"; export {
default as MetricsTable,
type MetricsRow,
} from "./metrics-table.svelte";
export { default as SourceTable } from "./source-table.svelte"; export { default as SourceTable } from "./source-table.svelte";

View File

@@ -14,7 +14,6 @@ import { toast } from "svelte-sonner";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { resolve } from "$app/paths"; import { resolve } from "$app/paths";
import { api } from "$lib/api/client"; import { api } from "$lib/api/client";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
import { Alert, AlertDescription } from "$lib/components/ui/alert"; import { Alert, AlertDescription } from "$lib/components/ui/alert";
import { Badge } from "$lib/components/ui/badge"; import { Badge } from "$lib/components/ui/badge";
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
@@ -25,6 +24,7 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "$lib/components/ui/card"; } from "$lib/components/ui/card";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
import { Input } from "$lib/components/ui/input"; import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label"; import { Label } from "$lib/components/ui/label";

View File

@@ -12,7 +12,6 @@ import { formatRelativeTime } from "@reviq/common";
import { createQuery, useQueryClient } from "@tanstack/svelte-query"; import { createQuery, useQueryClient } from "@tanstack/svelte-query";
import { toast } from "svelte-sonner"; import { toast } from "svelte-sonner";
import { api } from "$lib/api/client"; import { api } from "$lib/api/client";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
import { Alert, AlertDescription } from "$lib/components/ui/alert"; import { Alert, AlertDescription } from "$lib/components/ui/alert";
import { Badge } from "$lib/components/ui/badge"; import { Badge } from "$lib/components/ui/badge";
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
@@ -23,6 +22,7 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "$lib/components/ui/card"; } from "$lib/components/ui/card";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
const queryClient = useQueryClient(); const queryClient = useQueryClient();

View File

@@ -15,7 +15,6 @@ import { createQuery, useQueryClient } from "@tanstack/svelte-query";
import { toast } from "svelte-sonner"; import { toast } from "svelte-sonner";
import { UAParser } from "ua-parser-js"; import { UAParser } from "ua-parser-js";
import { api } from "$lib/api/client"; import { api } from "$lib/api/client";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
import { Alert, AlertDescription } from "$lib/components/ui/alert"; import { Alert, AlertDescription } from "$lib/components/ui/alert";
import { Badge } from "$lib/components/ui/badge"; import { Badge } from "$lib/components/ui/badge";
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
@@ -26,6 +25,7 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "$lib/components/ui/card"; } from "$lib/components/ui/card";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
const queryClient = useQueryClient(); const queryClient = useQueryClient();

View File

@@ -6,7 +6,6 @@ import { toast } from "svelte-sonner";
import { resolve } from "$app/paths"; import { resolve } from "$app/paths";
import { api } from "$lib/api/client.js"; import { api } from "$lib/api/client.js";
import { AdminLayout } from "$lib/components/layout"; import { AdminLayout } from "$lib/components/layout";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
import { Button } from "$lib/components/ui/button/index.js"; import { Button } from "$lib/components/ui/button/index.js";
import { import {
Card, Card,
@@ -14,6 +13,7 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "$lib/components/ui/card/index.js"; } from "$lib/components/ui/card/index.js";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
import { Skeleton } from "$lib/components/ui/skeleton/index.js"; import { Skeleton } from "$lib/components/ui/skeleton/index.js";
import { import {
Table, Table,

View File

@@ -17,7 +17,6 @@ import { page } from "$app/state";
import { api } from "$lib/api/client"; import { api } from "$lib/api/client";
import { AdminLayout } from "$lib/components/layout"; import { AdminLayout } from "$lib/components/layout";
import { OrgAvatar } from "$lib/components/org"; import { OrgAvatar } from "$lib/components/org";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
import { Alert, AlertDescription } from "$lib/components/ui/alert"; import { Alert, AlertDescription } from "$lib/components/ui/alert";
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
import { import {
@@ -28,6 +27,7 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "$lib/components/ui/card"; } from "$lib/components/ui/card";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
import { Input } from "$lib/components/ui/input"; import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label"; import { Label } from "$lib/components/ui/label";
import { import {

View File

@@ -13,7 +13,6 @@ import { toast } from "svelte-sonner";
import { api } from "$lib/api/client"; import { api } from "$lib/api/client";
import { DashboardLayout } from "$lib/components/layout"; import { DashboardLayout } from "$lib/components/layout";
import { RoleBadge } from "$lib/components/org"; import { RoleBadge } from "$lib/components/org";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
import { import {
Card, Card,
@@ -21,6 +20,7 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "$lib/components/ui/card"; } from "$lib/components/ui/card";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
import { Input } from "$lib/components/ui/input"; import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label"; import { Label } from "$lib/components/ui/label";
import { import {

View File

@@ -14,7 +14,6 @@ import { goto } from "$app/navigation";
import { resolve } from "$app/paths"; import { resolve } from "$app/paths";
import { api } from "$lib/api/client"; import { api } from "$lib/api/client";
import { SettingsLayout } from "$lib/components/layout"; import { SettingsLayout } from "$lib/components/layout";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
import { Alert, AlertDescription } from "$lib/components/ui/alert"; import { Alert, AlertDescription } from "$lib/components/ui/alert";
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
import { import {
@@ -24,6 +23,7 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "$lib/components/ui/card"; } from "$lib/components/ui/card";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
import { Input } from "$lib/components/ui/input"; import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label"; import { Label } from "$lib/components/ui/label";

View File

@@ -13,7 +13,6 @@ import { toast } from "svelte-sonner";
import { api } from "$lib/api/client"; import { api } from "$lib/api/client";
import { SettingsLayout } from "$lib/components/layout"; import { SettingsLayout } from "$lib/components/layout";
import { RoleBadge } from "$lib/components/org"; import { RoleBadge } from "$lib/components/org";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
import { import {
Card, Card,
@@ -21,6 +20,7 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "$lib/components/ui/card"; } from "$lib/components/ui/card";
import { ConfirmDialog } from "$lib/components/ui/confirm-dialog";
import { Input } from "$lib/components/ui/input"; import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label"; import { Label } from "$lib/components/ui/label";
import { import {

View File

@@ -15,15 +15,19 @@ const describeE2E = describe.skipIf(SKIP_DB_TESTS);
describe("createDb", () => { describe("createDb", () => {
test("throws error for empty connection string", () => { test("throws error for empty connection string", () => {
expect(() => createDb("")).toThrow("Database connection string is required"); expect(() => createDb("")).toThrow(
"Database connection string is required",
);
}); });
test("throws error for null-ish connection string", () => { test("throws error for null-ish connection string", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument -- testing edge case // biome-ignore lint/suspicious/noExplicitAny: testing edge case with invalid input
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
expect(() => createDb(null as any)).toThrow( expect(() => createDb(null as any)).toThrow(
"Database connection string is required", "Database connection string is required",
); );
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument -- testing edge case // biome-ignore lint/suspicious/noExplicitAny: testing edge case with invalid input
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
expect(() => createDb(undefined as any)).toThrow( expect(() => createDb(undefined as any)).toThrow(
"Database connection string is required", "Database connection string is required",
); );

View File

@@ -42,7 +42,7 @@ let testCounter = 0;
const uniqueTestId = (): string => { const uniqueTestId = (): string => {
const timestamp = Date.now(); const timestamp = Date.now();
testCounter++; testCounter++;
return `${timestamp}-${testCounter.toString()}`; return `${timestamp.toString()}-${testCounter.toString()}`;
}; };
/** Truncate all tables */ /** Truncate all tables */
@@ -95,10 +95,8 @@ describeE2E("[e2e] executeBootstrap", () => {
}); });
afterAll(async () => { afterAll(async () => {
if (db) { await truncateAllTables(db);
await truncateAllTables(db); await db.destroy();
await db.destroy();
}
}); });
test("creates superuser with correct email and password", async () => { test("creates superuser with correct email and password", async () => {
@@ -263,6 +261,7 @@ describeE2E("[e2e] executeBootstrap", () => {
test("throws error for password less than 8 characters", async () => { test("throws error for password less than 8 characters", async () => {
await withTestTransaction(db, async (trx) => { await withTestTransaction(db, async (trx) => {
// eslint-disable-next-line @typescript-eslint/await-thenable, @typescript-eslint/no-confusing-void-expression
await expect( await expect(
executeBootstrap(trx, { executeBootstrap(trx, {
email: "admin@example.com", email: "admin@example.com",
@@ -274,6 +273,7 @@ describeE2E("[e2e] executeBootstrap", () => {
test("throws error for password exactly 7 characters", async () => { test("throws error for password exactly 7 characters", async () => {
await withTestTransaction(db, async (trx) => { await withTestTransaction(db, async (trx) => {
// eslint-disable-next-line @typescript-eslint/await-thenable, @typescript-eslint/no-confusing-void-expression
await expect( await expect(
executeBootstrap(trx, { executeBootstrap(trx, {
email: "admin@example.com", email: "admin@example.com",
@@ -296,6 +296,7 @@ describeE2E("[e2e] executeBootstrap", () => {
test("throws error for invalid email without @", async () => { test("throws error for invalid email without @", async () => {
await withTestTransaction(db, async (trx) => { await withTestTransaction(db, async (trx) => {
// eslint-disable-next-line @typescript-eslint/await-thenable, @typescript-eslint/no-confusing-void-expression
await expect( await expect(
executeBootstrap(trx, { executeBootstrap(trx, {
email: "invalidemail", email: "invalidemail",
@@ -326,6 +327,7 @@ describeE2E("[e2e] executeBootstrap", () => {
}); });
// Attempt to create the same user again // Attempt to create the same user again
// eslint-disable-next-line @typescript-eslint/await-thenable, @typescript-eslint/no-confusing-void-expression
await expect( await expect(
executeBootstrap(trx, { executeBootstrap(trx, {
email: "admin@example.com", email: "admin@example.com",
@@ -552,19 +554,19 @@ describeE2E("[e2e] executeBootstrap", () => {
}); });
// Create another org and invite // Create another org and invite
const [otherOrg] = await trx const otherOrg = await trx
.insertInto("orgs") .insertInto("orgs")
.values({ .values({
slug: `other-org-${uniqueId}`, slug: `other-org-${uniqueId}`,
display_name: "Other Org", display_name: "Other Org",
}) })
.returning(["id"]) .returning(["id"])
.execute(); .executeTakeFirstOrThrow();
await trx await trx
.insertInto("org_invites") .insertInto("org_invites")
.values({ .values({
org_id: otherOrg!.id, org_id: otherOrg.id,
email: "invitee@example.com", email: "invitee@example.com",
role: "member", role: "member",
invited_by: result1.user.id, invited_by: result1.user.id,
@@ -611,14 +613,14 @@ describeE2E("[e2e] executeBootstrap", () => {
.execute(); .execute();
// Add org invites (to the org, not by the user) // Add org invites (to the org, not by the user)
const [anotherUser] = await trx const anotherUser = await trx
.insertInto("users") .insertInto("users")
.values({ .values({
email: `other-${uniqueId}@example.com`, email: `other-${uniqueId}@example.com`,
display_name: "Other User", display_name: "Other User",
}) })
.returning(["id"]) .returning(["id"])
.execute(); .executeTakeFirstOrThrow();
await trx await trx
.insertInto("org_invites") .insertInto("org_invites")
@@ -626,7 +628,7 @@ describeE2E("[e2e] executeBootstrap", () => {
org_id: result1.org.id, org_id: result1.org.id,
email: "invitee@example.com", email: "invitee@example.com",
role: "member", role: "member",
invited_by: anotherUser!.id, invited_by: anotherUser.id,
token: "invite-token-2", token: "invite-token-2",
expires_at: new Date(Date.now() + 86400000), expires_at: new Date(Date.now() + 86400000),
}) })

View File

@@ -30,5 +30,5 @@ export async function withTransaction<T>(
} }
// Not in a transaction, start one // Not in a transaction, start one
return (db as Kysely<Database>).transaction().execute(callback); return db.transaction().execute(callback);
} }

View File

@@ -202,9 +202,11 @@ export async function runMigrations(): Promise<void> {
await client.query(schemaSql); await client.query(schemaSql);
} catch (error) { } catch (error) {
// Ignore "already exists" errors - schema may have already been applied // Ignore "already exists" errors - schema may have already been applied
const message = error instanceof Error ? error.message : String(error); const message = error instanceof Error ? error.message : "Unknown error";
if (!message.includes("already exists")) { if (!message.includes("already exists")) {
throw new Error(`Schema application failed: ${message}`); throw error instanceof Error
? error
: new Error(`Schema application failed: ${message}`);
} }
} finally { } finally {
await client.end(); await client.end();