mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-06 04:19:20 +00:00
358 lines
9.2 KiB
TypeScript
358 lines
9.2 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { exec } from "node:child_process";
|
|
import { promisify } from "node:util";
|
|
|
|
const execAsync = promisify(exec);
|
|
|
|
let tmpDir = ".";
|
|
|
|
describe("info command", () => {
|
|
beforeEach(async () => {
|
|
const tmp = path.join(
|
|
process.cwd(),
|
|
"node_modules",
|
|
".cache",
|
|
"info_test-",
|
|
);
|
|
await fs.mkdir(path.join(tmp, "node_modules", ".cache"), {
|
|
recursive: true,
|
|
});
|
|
tmpDir = await fs.mkdtemp(tmp);
|
|
|
|
// Mock console methods to avoid noise in test output
|
|
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await fs.rm(tmpDir, { recursive: true });
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("should display system information without auth config", async () => {
|
|
// Create a minimal package.json
|
|
await fs.writeFile(
|
|
path.join(tmpDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"better-auth": "^1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
const cliPath = path.join(process.cwd(), "dist", "index.mjs");
|
|
const { stdout } = await execAsync(`node ${cliPath} info --json`, {
|
|
cwd: tmpDir,
|
|
});
|
|
|
|
const output = JSON.parse(stdout);
|
|
|
|
// Check system information
|
|
expect(output.system).toHaveProperty("platform");
|
|
expect(output.system).toHaveProperty("arch");
|
|
expect(output.system).toHaveProperty("cpuCount");
|
|
expect(output.system).toHaveProperty("totalMemory");
|
|
|
|
// Check node information
|
|
expect(output.node).toHaveProperty("version");
|
|
expect(output.node).toHaveProperty("env");
|
|
|
|
// Check package manager
|
|
expect(output.packageManager).toHaveProperty("name");
|
|
expect(output.packageManager).toHaveProperty("version");
|
|
|
|
// Better Auth config should have an error since no auth file exists
|
|
expect(output.betterAuth).toHaveProperty("version");
|
|
expect(output.betterAuth.config).toBeNull();
|
|
});
|
|
|
|
it("should load and sanitize auth configuration", async () => {
|
|
// Create package.json with dependencies
|
|
await fs.writeFile(
|
|
path.join(tmpDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"better-auth": "^1.0.0",
|
|
next: "^14.0.0",
|
|
react: "^18.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
// Create auth.ts with sensitive data - using in-memory database to avoid adapter errors
|
|
await fs.writeFile(
|
|
path.join(tmpDir, "auth.ts"),
|
|
`import { betterAuth } from "better-auth";
|
|
|
|
export const auth = betterAuth({
|
|
secret: "super-secret-key-123",
|
|
baseURL: "https://example.com",
|
|
emailAndPassword: {
|
|
enabled: true,
|
|
},
|
|
socialProviders: {
|
|
github: {
|
|
clientId: "github-client-id",
|
|
clientSecret: "github-client-secret"
|
|
},
|
|
google: {
|
|
clientId: "google-client-id",
|
|
clientSecret: "google-client-secret"
|
|
}
|
|
}
|
|
})`,
|
|
);
|
|
|
|
const cliPath = path.join(process.cwd(), "dist", "index.mjs");
|
|
const { stdout } = await execAsync(`node ${cliPath} info --json`, {
|
|
cwd: tmpDir,
|
|
});
|
|
|
|
const output = JSON.parse(stdout);
|
|
|
|
// Check that sensitive data is redacted
|
|
expect(output.betterAuth.config).toBeDefined();
|
|
expect(output.betterAuth.config.secret).toBe("[REDACTED]");
|
|
|
|
// Check social providers are sanitized
|
|
expect(output.betterAuth.config.socialProviders).toBeDefined();
|
|
expect(output.betterAuth.config.socialProviders.github.clientId).toBe(
|
|
"[REDACTED]",
|
|
);
|
|
expect(output.betterAuth.config.socialProviders.github.clientSecret).toBe(
|
|
"[REDACTED]",
|
|
);
|
|
expect(output.betterAuth.config.socialProviders.google.clientId).toBe(
|
|
"[REDACTED]",
|
|
);
|
|
expect(output.betterAuth.config.socialProviders.google.clientSecret).toBe(
|
|
"[REDACTED]",
|
|
);
|
|
|
|
// Check non-sensitive data is preserved
|
|
expect(output.betterAuth.config.emailAndPassword).toEqual({
|
|
enabled: true,
|
|
});
|
|
expect(output.betterAuth.config.baseURL).toBe("https://example.com");
|
|
});
|
|
|
|
it("should detect installed frameworks", async () => {
|
|
// Create package.json with various frameworks
|
|
await fs.writeFile(
|
|
path.join(tmpDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"better-auth": "^1.0.0",
|
|
next: "^14.0.0",
|
|
react: "^18.0.0",
|
|
},
|
|
devDependencies: {
|
|
"@sveltejs/kit": "^2.0.0",
|
|
svelte: "^4.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
const cliPath = path.join(process.cwd(), "dist", "index.mjs");
|
|
const { stdout } = await execAsync(`node ${cliPath} info --json`, {
|
|
cwd: tmpDir,
|
|
});
|
|
|
|
const output = JSON.parse(stdout);
|
|
|
|
// Check frameworks are detected
|
|
expect(output.frameworks).toContainEqual({
|
|
name: "next",
|
|
version: "^14.0.0",
|
|
});
|
|
expect(output.frameworks).toContainEqual({
|
|
name: "react",
|
|
version: "^18.0.0",
|
|
});
|
|
expect(output.frameworks).toContainEqual({
|
|
name: "@sveltejs/kit",
|
|
version: "^2.0.0",
|
|
});
|
|
expect(output.frameworks).toContainEqual({
|
|
name: "svelte",
|
|
version: "^4.0.0",
|
|
});
|
|
});
|
|
|
|
it("should detect database clients", async () => {
|
|
// Create package.json with database clients
|
|
await fs.writeFile(
|
|
path.join(tmpDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"better-auth": "^1.0.0",
|
|
"@prisma/client": "^5.0.0",
|
|
kysely: "^0.26.0",
|
|
},
|
|
devDependencies: {
|
|
"drizzle-orm": "^0.29.0",
|
|
"better-sqlite3": "^9.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
const cliPath = path.join(process.cwd(), "dist", "index.mjs");
|
|
const { stdout } = await execAsync(`node ${cliPath} info --json`, {
|
|
cwd: tmpDir,
|
|
});
|
|
|
|
const output = JSON.parse(stdout);
|
|
|
|
// Check database clients are detected
|
|
expect(output.databases).toContainEqual({
|
|
name: "@prisma/client",
|
|
version: "^5.0.0",
|
|
});
|
|
expect(output.databases).toContainEqual({
|
|
name: "kysely",
|
|
version: "^0.26.0",
|
|
});
|
|
expect(output.databases).toContainEqual({
|
|
name: "drizzle",
|
|
version: "^0.29.0",
|
|
});
|
|
expect(output.databases).toContainEqual({
|
|
name: "better-sqlite3",
|
|
version: "^9.0.0",
|
|
});
|
|
});
|
|
|
|
it("should support custom config path", async () => {
|
|
// Create package.json
|
|
await fs.writeFile(
|
|
path.join(tmpDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"better-auth": "^1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
// Create custom directory for auth config
|
|
const customPath = path.join(tmpDir, "config");
|
|
await fs.mkdir(customPath, { recursive: true });
|
|
|
|
// Create auth config in custom location
|
|
await fs.writeFile(
|
|
path.join(customPath, "auth.config.ts"),
|
|
`import { betterAuth } from "better-auth";
|
|
|
|
export const auth = betterAuth({
|
|
secret: "my-secret",
|
|
appName: "Custom Config App",
|
|
emailAndPassword: {
|
|
enabled: true,
|
|
}
|
|
})`,
|
|
);
|
|
|
|
const cliPath = path.join(process.cwd(), "dist", "index.mjs");
|
|
const { stdout } = await execAsync(
|
|
`node ${cliPath} info --config config/auth.config.ts --json`,
|
|
{ cwd: tmpDir },
|
|
);
|
|
|
|
const output = JSON.parse(stdout);
|
|
|
|
// Check that custom config was loaded
|
|
expect(output.betterAuth.config).toBeDefined();
|
|
expect(output.betterAuth.config.appName).toBe("Custom Config App");
|
|
expect(output.betterAuth.config.secret).toBe("[REDACTED]");
|
|
expect(output.betterAuth.config.emailAndPassword).toEqual({
|
|
enabled: true,
|
|
});
|
|
});
|
|
|
|
it("should sanitize plugin configurations", async () => {
|
|
// Create package.json
|
|
await fs.writeFile(
|
|
path.join(tmpDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"better-auth": "^1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
// Create auth.ts with plugins
|
|
await fs.writeFile(
|
|
path.join(tmpDir, "auth.ts"),
|
|
`import { betterAuth } from "better-auth";
|
|
import { twoFactor, organization } from "better-auth/plugins";
|
|
|
|
export const auth = betterAuth({
|
|
plugins: [
|
|
twoFactor({
|
|
otpOptions: {
|
|
secret: "otp-secret-key"
|
|
}
|
|
}),
|
|
organization({
|
|
apiKey: "org-api-key",
|
|
webhookSecret: "webhook-secret"
|
|
})
|
|
]
|
|
})`,
|
|
);
|
|
|
|
const cliPath = path.join(process.cwd(), "dist", "index.mjs");
|
|
const { stdout } = await execAsync(`node ${cliPath} info --json`, {
|
|
cwd: tmpDir,
|
|
});
|
|
|
|
const output = JSON.parse(stdout);
|
|
|
|
// Check that plugin configs are sanitized
|
|
expect(output.betterAuth.config.plugins).toBeDefined();
|
|
expect(Array.isArray(output.betterAuth.config.plugins)).toBe(true);
|
|
|
|
// Plugin sensitive data should be redacted
|
|
const plugins = output.betterAuth.config.plugins;
|
|
plugins.forEach((plugin: any) => {
|
|
if (plugin.config) {
|
|
// Check that sensitive keys are redacted
|
|
const configStr = JSON.stringify(plugin.config);
|
|
expect(configStr).toContain("[REDACTED]");
|
|
}
|
|
});
|
|
});
|
|
|
|
it("should handle missing package.json gracefully", async () => {
|
|
// Don't create package.json
|
|
const cliPath = path.join(process.cwd(), "dist", "index.mjs");
|
|
const { stdout } = await execAsync(`node ${cliPath} info --json`, {
|
|
cwd: tmpDir,
|
|
});
|
|
|
|
const output = JSON.parse(stdout);
|
|
|
|
// Should still return system info
|
|
expect(output.system).toBeDefined();
|
|
expect(output.node).toBeDefined();
|
|
expect(output.packageManager).toBeDefined();
|
|
|
|
// Frameworks and databases should be null
|
|
expect(output.frameworks).toBeNull();
|
|
expect(output.databases).toBeNull();
|
|
});
|
|
}, 20000);
|