Files
better-auth/packages/cli/test/get-config.test.ts

887 lines
20 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { test } from "vitest";
import fs from "node:fs/promises";
import path from "node:path";
import { getConfig } from "../src/utils/get-config";
interface TmpDirFixture {
tmpdir: string;
}
async function createTempDir() {
const tmpdir = path.join(process.cwd(), "test", "getConfig_test-");
return await fs.mkdtemp(tmpdir);
}
export const tmpdirTest = test.extend<TmpDirFixture>({
tmpdir: async ({}, use) => {
const directory = await createTempDir();
await use(directory);
await fs.rm(directory, { recursive: true });
},
});
let tmpDir = ".";
describe("getConfig", async () => {
beforeEach(async () => {
const tmp = path.join(process.cwd(), "getConfig_test-");
tmpDir = await fs.mkdtemp(tmp);
});
afterEach(async () => {
await fs.rm(tmpDir, { recursive: true });
});
it("should resolve resolver type alias", async () => {
const authPath = path.join(tmpDir, "server", "auth");
const dbPath = path.join(tmpDir, "server", "db");
await fs.mkdir(authPath, { recursive: true });
await fs.mkdir(dbPath, { recursive: true });
//create dummy tsconfig.json
await fs.writeFile(
path.join(tmpDir, "tsconfig.json"),
`{
"compilerOptions": {
/* Path Aliases */
"baseUrl": ".",
"paths": {
"@server/*": ["./server/*"]
}
}
}`,
);
//create dummy auth.ts
await fs.writeFile(
path.join(authPath, "auth.ts"),
`import {betterAuth} from "better-auth";
import {prismaAdapter} from "better-auth/adapters/prisma";
import {db} from "@server/db/db";
export const auth = betterAuth({
database: prismaAdapter(db, {
provider: 'sqlite'
}),
emailAndPassword: {
enabled: true,
}
})`,
);
//create dummy db.ts
await fs.writeFile(
path.join(dbPath, "db.ts"),
`class PrismaClient {
constructor() {}
}
export const db = new PrismaClient()`,
);
const config = await getConfig({
cwd: tmpDir,
configPath: "server/auth/auth.ts",
});
expect(config).not.toBe(null);
});
it("should resolve direct alias", async () => {
const authPath = path.join(tmpDir, "server", "auth");
const dbPath = path.join(tmpDir, "server", "db");
await fs.mkdir(authPath, { recursive: true });
await fs.mkdir(dbPath, { recursive: true });
//create dummy tsconfig.json
await fs.writeFile(
path.join(tmpDir, "tsconfig.json"),
`{
"compilerOptions": {
/* Path Aliases */
"baseUrl": ".",
"paths": {
"prismaDbClient": ["./server/db/db"]
}
}
}`,
);
//create dummy auth.ts
await fs.writeFile(
path.join(authPath, "auth.ts"),
`import {betterAuth} from "better-auth";
import {prismaAdapter} from "better-auth/adapters/prisma";
import {db} from "prismaDbClient";
export const auth = betterAuth({
database: prismaAdapter(db, {
provider: 'sqlite'
}),
emailAndPassword: {
enabled: true,
}
})`,
);
//create dummy db.ts
await fs.writeFile(
path.join(dbPath, "db.ts"),
`class PrismaClient {
constructor() {}
}
export const db = new PrismaClient()`,
);
const config = await getConfig({
cwd: tmpDir,
configPath: "server/auth/auth.ts",
});
expect(config).not.toBe(null);
});
it("should resolve resolver type alias with relative path", async () => {
const authPath = path.join(tmpDir, "test", "server", "auth");
const dbPath = path.join(tmpDir, "test", "server", "db");
await fs.mkdir(authPath, { recursive: true });
await fs.mkdir(dbPath, { recursive: true });
//create dummy tsconfig.json
await fs.writeFile(
path.join(tmpDir, "tsconfig.json"),
`{
"compilerOptions": {
/* Path Aliases */
"baseUrl": "./test",
"paths": {
"@server/*": ["./server/*"]
}
}
}`,
);
//create dummy auth.ts
await fs.writeFile(
path.join(authPath, "auth.ts"),
`import {betterAuth} from "better-auth";
import {prismaAdapter} from "better-auth/adapters/prisma";
import {db} from "@server/db/db";
export const auth = betterAuth({
database: prismaAdapter(db, {
provider: 'sqlite'
}),
emailAndPassword: {
enabled: true,
}
})`,
);
//create dummy db.ts
await fs.writeFile(
path.join(dbPath, "db.ts"),
`class PrismaClient {
constructor() {}
}
export const db = new PrismaClient()`,
);
const config = await getConfig({
cwd: tmpDir,
configPath: "test/server/auth/auth.ts",
});
expect(config).not.toBe(null);
});
it("should resolve direct alias with relative path", async () => {
const authPath = path.join(tmpDir, "test", "server", "auth");
const dbPath = path.join(tmpDir, "test", "server", "db");
await fs.mkdir(authPath, { recursive: true });
await fs.mkdir(dbPath, { recursive: true });
//create dummy tsconfig.json
await fs.writeFile(
path.join(tmpDir, "tsconfig.json"),
`{
"compilerOptions": {
/* Path Aliases */
"baseUrl": "./test",
"paths": {
"prismaDbClient": ["./server/db/db"]
}
}
}`,
);
//create dummy auth.ts
await fs.writeFile(
path.join(authPath, "auth.ts"),
`import {betterAuth} from "better-auth";
import {prismaAdapter} from "better-auth/adapters/prisma";
import {db} from "prismaDbClient";
export const auth = betterAuth({
database: prismaAdapter(db, {
provider: 'sqlite'
}),
emailAndPassword: {
enabled: true,
}
})`,
);
//create dummy db.ts
await fs.writeFile(
path.join(dbPath, "db.ts"),
`class PrismaClient {
constructor() {}
}
export const db = new PrismaClient()`,
);
const config = await getConfig({
cwd: tmpDir,
configPath: "test/server/auth/auth.ts",
});
expect(config).not.toBe(null);
});
it("should resolve with relative import", async () => {
const authPath = path.join(tmpDir, "test", "server", "auth");
const dbPath = path.join(tmpDir, "test", "server", "db");
await fs.mkdir(authPath, { recursive: true });
await fs.mkdir(dbPath, { recursive: true });
//create dummy tsconfig.json
await fs.writeFile(
path.join(tmpDir, "tsconfig.json"),
`{
"compilerOptions": {
/* Path Aliases */
"baseUrl": "./test",
"paths": {
"prismaDbClient": ["./server/db/db"]
}
}
}`,
);
//create dummy auth.ts
await fs.writeFile(
path.join(authPath, "auth.ts"),
`import {betterAuth} from "better-auth";
import {prismaAdapter} from "better-auth/adapters/prisma";
import {db} from "../db/db";
export const auth = betterAuth({
database: prismaAdapter(db, {
provider: 'sqlite'
}),
emailAndPassword: {
enabled: true,
}
})`,
);
//create dummy db.ts
await fs.writeFile(
path.join(dbPath, "db.ts"),
`class PrismaClient {
constructor() {}
}
export const db = new PrismaClient()`,
);
const config = await getConfig({
cwd: tmpDir,
configPath: "test/server/auth/auth.ts",
});
expect(config).not.toBe(null);
});
it("should error with invalid alias", async () => {
const authPath = path.join(tmpDir, "server", "auth");
const dbPath = path.join(tmpDir, "server", "db");
await fs.mkdir(authPath, { recursive: true });
await fs.mkdir(dbPath, { recursive: true });
//create dummy tsconfig.json
await fs.writeFile(
path.join(tmpDir, "tsconfig.json"),
`{
"compilerOptions": {
/* Path Aliases */
"baseUrl": ".",
"paths": {
"@server/*": ["./PathIsInvalid/*"]
}
}
}`,
);
//create dummy auth.ts
await fs.writeFile(
path.join(authPath, "auth.ts"),
`import {betterAuth} from "better-auth";
import {prismaAdapter} from "better-auth/adapters/prisma";
import {db} from "@server/db/db";
export const auth = betterAuth({
database: prismaAdapter(db, {
provider: 'sqlite'
}),
emailAndPassword: {
enabled: true,
}
})`,
);
//create dummy db.ts
await fs.writeFile(
path.join(dbPath, "db.ts"),
`class PrismaClient {
constructor() {}
}
export const db = new PrismaClient()`,
);
const consoleErrorSpy = vi
.spyOn(console, "error")
.mockImplementation(() => {});
await expect(() =>
getConfig({ cwd: tmpDir, configPath: "server/auth/auth.ts" }),
).rejects.toThrowError();
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining("Couldn't read your auth config."),
expect.objectContaining({
code: "MODULE_NOT_FOUND",
}),
);
});
it("should resolve js config", async () => {
const authPath = path.join(tmpDir, "server", "auth");
const dbPath = path.join(tmpDir, "server", "db");
await fs.mkdir(authPath, { recursive: true });
await fs.mkdir(dbPath, { recursive: true });
//create dummy auth.ts
await fs.writeFile(
path.join(authPath, "auth.js"),
`import { betterAuth } from "better-auth";
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
}
})`,
);
const config = await getConfig({
cwd: tmpDir,
configPath: "server/auth/auth.js",
});
expect(config).toMatchObject({
emailAndPassword: { enabled: true },
});
});
it("should resolve path aliases from referenced tsconfig files", async () => {
const authPath = path.join(tmpDir, "apps", "web", "server", "auth");
const dbPath = path.join(tmpDir, "packages", "shared", "db");
await fs.mkdir(authPath, { recursive: true });
await fs.mkdir(dbPath, { recursive: true });
// Create root tsconfig.json with references
await fs.writeFile(
path.join(tmpDir, "tsconfig.json"),
`{
"references": [
{ "path": "./apps/web" },
{ "path": "./packages/shared" }
]
}`,
);
// Create web app tsconfig.json with aliases
await fs.writeFile(
path.join(tmpDir, "apps", "web", "tsconfig.json"),
`{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@web/*": ["./server/*"]
}
}
}`,
);
// Create shared package tsconfig.json with aliases
await fs.writeFile(
path.join(tmpDir, "packages", "shared", "tsconfig.json"),
`{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@shared/*": ["./db/*"]
}
}
}`,
);
// Create dummy auth.ts using both aliases
await fs.writeFile(
path.join(authPath, "auth.ts"),
`import {betterAuth} from "better-auth";
import {prismaAdapter} from "better-auth/adapters/prisma";
import {db} from "@shared/db";
export const auth = betterAuth({
database: prismaAdapter(db, {
provider: 'sqlite'
}),
emailAndPassword: {
enabled: true,
}
})`,
);
// Create dummy db.ts
await fs.writeFile(
path.join(dbPath, "db.ts"),
`class PrismaClient {
constructor() {}
}
export const db = new PrismaClient()`,
);
const config = await getConfig({
cwd: tmpDir,
configPath: "apps/web/server/auth/auth.ts",
});
expect(config).toMatchObject({
emailAndPassword: { enabled: true },
database: expect.objectContaining({
// This proves the @shared/db alias was resolved correctly
}),
});
});
it("should handle missing referenced tsconfig files gracefully", async () => {
const authPath = path.join(tmpDir, "server", "auth");
await fs.mkdir(authPath, { recursive: true });
// Create root tsconfig.json with reference to non-existent file
await fs.writeFile(
path.join(tmpDir, "tsconfig.json"),
`{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@server/*": ["./server/*"]
}
},
"references": [
{ "path": "./non-existent" }
]
}`,
);
// Create dummy auth.ts
await fs.writeFile(
path.join(authPath, "auth.ts"),
`import {betterAuth} from "better-auth";
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
}
})`,
);
const config = await getConfig({
cwd: tmpDir,
configPath: "server/auth/auth.ts",
});
expect(config).not.toBe(null);
});
it("should handle circular references in tsconfig files", async () => {
const authPath = path.join(tmpDir, "server", "auth");
const appPath = path.join(tmpDir, "app");
await fs.mkdir(authPath, { recursive: true });
await fs.mkdir(appPath, { recursive: true });
// Create root tsconfig.json that references app tsconfig
await fs.writeFile(
path.join(tmpDir, "tsconfig.json"),
`{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@root/*": ["./server/*"]
}
},
"references": [
{ "path": "./app" }
]
}`,
);
// Create app tsconfig.json that references back to root
await fs.writeFile(
path.join(tmpDir, "app", "tsconfig.json"),
`{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@app/*": ["./src/*"]
}
},
"references": [
{ "path": ".." }
]
}`,
);
// Create dummy auth.ts
await fs.writeFile(
path.join(authPath, "auth.ts"),
`import {betterAuth} from "better-auth";
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
}
})`,
);
const config = await getConfig({
cwd: tmpDir,
configPath: "server/auth/auth.ts",
});
expect(config).not.toBe(null);
});
it("should resolve direct tsconfig file references", async () => {
const authPath = path.join(tmpDir, "server", "auth");
const sharedPath = path.join(tmpDir, "shared", "db");
await fs.mkdir(authPath, { recursive: true });
await fs.mkdir(sharedPath, { recursive: true });
// Create root tsconfig.json with direct file references
await fs.writeFile(
path.join(tmpDir, "tsconfig.json"),
`{
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.shared.json" }
]
}`,
);
// Create tsconfig.app.json with app-specific aliases
await fs.writeFile(
path.join(tmpDir, "tsconfig.app.json"),
`{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@app/*": ["./server/*"]
}
}
}`,
);
// Create tsconfig.shared.json with shared aliases
await fs.writeFile(
path.join(tmpDir, "tsconfig.shared.json"),
`{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@shared/*": ["./shared/*"]
}
}
}`,
);
// Create dummy auth.ts using both aliases
await fs.writeFile(
path.join(authPath, "auth.ts"),
`import {betterAuth} from "better-auth";
import {prismaAdapter} from "better-auth/adapters/prisma";
import {db} from "@shared/db/db";
export const auth = betterAuth({
database: prismaAdapter(db, {
provider: 'sqlite'
}),
emailAndPassword: {
enabled: true,
}
})`,
);
// Create dummy db.ts
await fs.writeFile(
path.join(sharedPath, "db.ts"),
`class PrismaClient {
constructor() {}
}
export const db = new PrismaClient()`,
);
const config = await getConfig({
cwd: tmpDir,
configPath: "server/auth/auth.ts",
});
expect(config).not.toBe(null);
});
it("should handle mixed directory and file references", async () => {
const authPath = path.join(tmpDir, "apps", "web", "server", "auth");
const utilsPath = path.join(tmpDir, "packages", "utils");
await fs.mkdir(authPath, { recursive: true });
await fs.mkdir(utilsPath, { recursive: true });
// Create root tsconfig.json with mixed references
await fs.writeFile(
path.join(tmpDir, "tsconfig.json"),
`{
"references": [
{ "path": "./apps/web" },
{ "path": "./tsconfig.utils.json" }
]
}`,
);
// Create web app directory-based tsconfig
await fs.writeFile(
path.join(tmpDir, "apps", "web", "tsconfig.json"),
`{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@web/*": ["./server/*"]
}
}
}`,
);
// Create utils file-based tsconfig
await fs.writeFile(
path.join(tmpDir, "tsconfig.utils.json"),
`{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@utils/*": ["./packages/utils/*"]
}
}
}`,
);
// Create dummy auth.ts
await fs.writeFile(
path.join(authPath, "auth.ts"),
`import {betterAuth} from "better-auth";
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
}
})`,
);
// Create dummy utils file
await fs.writeFile(
path.join(utilsPath, "index.ts"),
`export const utils = {}`,
);
const config = await getConfig({
cwd: tmpDir,
configPath: "apps/web/server/auth/auth.ts",
});
expect(config).not.toBe(null);
});
it("should resolve SvelteKit $lib/server imports correctly", async () => {
const authPath = path.join(tmpDir, "src");
const libServerPath = path.join(tmpDir, "src", "lib", "server");
await fs.mkdir(authPath, { recursive: true });
await fs.mkdir(libServerPath, { recursive: true });
await fs.writeFile(
path.join(tmpDir, "package.json"),
JSON.stringify({
name: "test-sveltekit",
devDependencies: {
"@sveltejs/kit": "^2.0.0",
},
}),
);
await fs.writeFile(
path.join(libServerPath, "database.ts"),
`export const db = {
// Mock database client
connect: () => console.log('Connected to database')
};`,
);
await fs.writeFile(
path.join(authPath, "auth.ts"),
`import { betterAuth } from "better-auth";
import { db } from "$lib/server/database";
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
}
})`,
);
const config = await getConfig({
cwd: tmpDir,
configPath: "src/auth.ts",
});
expect(config).not.toBe(null);
expect(config).toMatchObject({
emailAndPassword: { enabled: true },
});
});
it("should resolve export default auth", async () => {
const authPath = path.join(tmpDir, "server", "auth");
await fs.mkdir(authPath, { recursive: true });
await fs.writeFile(
path.join(authPath, "auth.ts"),
`import { betterAuth } from "better-auth";
const auth = betterAuth({
emailAndPassword: {
enabled: true,
},
socialProviders: {
github: {
clientId: "test-id",
clientSecret: "test-secret"
}
}
});
export default auth;`,
);
const config = await getConfig({
cwd: tmpDir,
configPath: "server/auth/auth.ts",
});
expect(config).not.toBe(null);
expect(config).toMatchObject({
emailAndPassword: { enabled: true },
socialProviders: {
github: {
clientId: "test-id",
clientSecret: "test-secret",
},
},
});
});
it("should resolve export default auth with named export", async () => {
const authPath = path.join(tmpDir, "server", "auth");
await fs.mkdir(authPath, { recursive: true });
await fs.writeFile(
path.join(authPath, "auth.ts"),
`import { betterAuth } from "better-auth";
const auth = betterAuth({
emailAndPassword: {
enabled: true,
},
socialProviders: {
github: {
clientId: "test-id",
clientSecret: "test-secret"
}
}
});
export { auth };
export default auth;`,
);
const config = await getConfig({
cwd: tmpDir,
configPath: "server/auth/auth.ts",
});
expect(config).not.toBe(null);
expect(config).toMatchObject({
emailAndPassword: { enabled: true },
socialProviders: {
github: {
clientId: "test-id",
clientSecret: "test-secret",
},
},
});
});
it("should resolve export default with inline betterAuth call", async () => {
const authPath = path.join(tmpDir, "server", "auth");
await fs.mkdir(authPath, { recursive: true });
await fs.writeFile(
path.join(authPath, "auth.ts"),
`import { betterAuth } from "better-auth";
export default betterAuth({
emailAndPassword: {
enabled: true,
},
trustedOrigins: ["http://localhost:3000"]
});`,
);
const config = await getConfig({
cwd: tmpDir,
configPath: "server/auth/auth.ts",
});
expect(config).not.toBe(null);
expect(config).toMatchObject({
emailAndPassword: { enabled: true },
trustedOrigins: ["http://localhost:3000"],
});
});
});