@convex-dev/better-auth
Version:
A Better Auth component for Convex.
407 lines • 12.6 kB
JavaScript
import { createClient } from "../client/index.js";
import { api } from "./_generated/api.js";
import { action } from "./_generated/server.js";
const getTestImports = async () => {
const vitestImportName = "vitest";
const { beforeEach, test, expect } = await import(vitestImportName);
const betterAuthAdaptersTestImportName = "better-auth/adapters/test";
const { runAdapterTest } = await import(betterAuthAdaptersTestImportName);
return { beforeEach, test, expect, runAdapterTest };
};
export const getAdapter = (ctx) => async (opts) => {
const authComponent = createClient(api, {
verbose: false,
});
const adapterFactory = authComponent.adapter(ctx);
const options = {
...(opts ?? {}),
user: {
...(opts?.user ?? {}),
// We don't currently support custom schema for tests, need to find a
// way to do this.
fields: undefined,
},
};
return adapterFactory(options);
};
// Tests need to run inside of a Convex function to use the Convex adapter
export const runTests = action(async (ctx, args) => {
const { runAdapterTest } = await getTestImports();
runAdapterTest({
getAdapter: getAdapter(ctx),
disableTests: args.disableTests,
});
});
export const runCustomTests = action(async (ctx, _args) => {
const { beforeEach, test, expect } = await getTestImports();
runCustomAdapterTests({
beforeEach,
test,
expect,
getAdapter: getAdapter(ctx),
});
});
function runCustomAdapterTests({ beforeEach, test, expect, getAdapter, }) {
beforeEach(async () => {
const adapter = await getAdapter();
await adapter.deleteMany({
model: "user",
where: [],
});
await adapter.deleteMany({
model: "session",
where: [],
});
});
test("should handle lone range operators", async () => {
const adapter = await getAdapter();
const user = await adapter.create({
model: "user",
data: {
name: "ab",
email: "a@a.com",
},
});
expect(await adapter.findMany({
model: "user",
where: [
{
field: "name",
operator: "lt",
value: "a",
},
],
})).toEqual([]);
expect(await adapter.findMany({
model: "user",
where: [
{
field: "name",
operator: "lte",
value: "a",
},
],
})).toEqual([]);
expect(await adapter.findMany({
model: "user",
where: [
{
field: "name",
operator: "gt",
value: "a",
},
],
})).toEqual([user]);
expect(await adapter.findMany({
model: "user",
where: [
{
field: "name",
operator: "gte",
value: "ab",
},
],
})).toEqual([user]);
});
test("should handle compound indexes that include id field", async () => {
const adapter = await getAdapter();
const user = await adapter.create({
model: "user",
data: {
name: "foo",
email: "foo@bar.com",
},
});
expect(await adapter.findOne({
model: "user",
where: [
{
field: "id",
value: user.id,
},
{
field: "name",
value: "wrong name",
},
],
})).toEqual(null);
expect(await adapter.findOne({
model: "user",
where: [
{
field: "id",
value: user.id,
},
{
field: "name",
value: "foo",
},
],
})).toEqual(user);
expect(await adapter.findOne({
model: "user",
where: [
{
field: "id",
value: user.id,
},
{
field: "name",
value: "foo",
operator: "lt",
},
],
})).toEqual(null);
expect(await adapter.findOne({
model: "user",
where: [
{
field: "id",
value: user.id,
},
{
field: "name",
value: "foo",
operator: "lte",
},
],
})).toEqual(user);
expect(await adapter.findOne({
model: "user",
where: [
{
field: "id",
value: user.id,
},
{
field: "name",
value: "foo",
operator: "gt",
},
],
})).toEqual(null);
expect(await adapter.findOne({
model: "user",
where: [
{
field: "id",
value: user.id,
},
{
field: "name",
value: "foo",
operator: "gte",
},
],
})).toEqual(user);
expect(await adapter.findOne({
model: "user",
where: [
{
field: "id",
value: user.id,
},
{
field: "name",
operator: "in",
value: ["wrong", "name"],
},
],
})).toEqual(null);
expect(await adapter.findOne({
model: "user",
where: [
{
field: "id",
value: user.id,
},
{
field: "name",
operator: "in",
value: ["foo"],
},
],
})).toEqual(user);
});
test("should automatically paginate", async () => {
const adapter = await getAdapter();
for (let i = 0; i < 300; i++) {
await adapter.create({
model: "user",
data: {
name: `foo${i}`,
email: `foo${i}@bar.com`,
},
});
}
// Better Auth defaults to a limit of 100
expect(await adapter.findMany({
model: "user",
})).toHaveLength(100);
// Pagination has a hardcoded numItems max of 200, this tests that it can handle
// specified limits beyond that
expect(await adapter.findMany({
model: "user",
limit: 250,
})).toHaveLength(250);
expect(await adapter.findMany({
model: "user",
limit: 350,
})).toHaveLength(300);
});
test("should handle OR where clauses", async () => {
const adapter = await getAdapter();
const user = await adapter.create({
model: "user",
data: {
name: "foo",
email: "foo@bar.com",
},
});
expect(await adapter.findOne({
model: "user",
where: [
{ field: "name", value: "bar", connector: "OR" },
{ field: "name", value: "foo", connector: "OR" },
],
})).toEqual(user);
});
test("should handle OR where clauses with sortBy", async () => {
const adapter = await getAdapter();
const fooUser = await adapter.create({
model: "user",
data: {
name: "foo",
email: "foo@bar.com",
},
});
const barUser = await adapter.create({
model: "user",
data: {
name: "bar",
email: "bar@bar.com",
},
});
await adapter.create({
model: "user",
data: {
name: "baz",
email: "baz@bar.com",
},
});
expect(await adapter.findMany({
model: "user",
where: [
{ field: "name", value: "bar", connector: "OR" },
{ field: "name", value: "foo", connector: "OR" },
],
sortBy: { field: "name", direction: "asc" },
})).toEqual([barUser, fooUser]);
expect(await adapter.findMany({
model: "user",
where: [
{ field: "name", value: "bar", connector: "OR" },
{ field: "name", value: "foo", connector: "OR" },
],
sortBy: { field: "name", direction: "desc" },
})).toEqual([fooUser, barUser]);
});
test("should handle count", async () => {
const adapter = await getAdapter();
await adapter.create({
model: "user",
data: {
name: "foo",
email: "foo@bar.com",
},
});
await adapter.create({
model: "user",
data: {
name: "bar",
email: "bar@bar.com",
},
});
expect(await adapter.count({
model: "user",
where: [{ field: "name", value: "foo" }],
})).toEqual(1);
});
test("should handle queries with no index", async () => {
const adapter = await getAdapter();
const user = await adapter.create({
model: "user",
data: {
name: "foo",
email: "foo@bar.com",
emailVerified: true,
},
});
expect(await adapter.findOne({
model: "user",
where: [{ field: "emailVerified", value: true }],
})).toEqual(user);
expect(await adapter.findOne({
model: "user",
where: [{ field: "emailVerified", value: false }],
})).toEqual(null);
});
test("should handle compound operator on non-unique field without an index", async () => {
const adapter = await getAdapter();
await adapter.create({
model: "account",
data: {
accountId: "foo",
providerId: "bar",
userId: "baz",
accessTokenExpiresAt: null,
createdAt: Date.now(),
updatedAt: Date.now(),
},
});
expect(await adapter.findOne({
model: "account",
where: [
{
operator: "lt",
connector: "AND",
field: "accessTokenExpiresAt",
value: Date.now(),
},
{
operator: "ne",
connector: "AND",
field: "accessTokenExpiresAt",
value: null,
},
],
})).toEqual(null);
});
test("should fail to create a record with a unique field that already exists", async () => {
const adapter = await getAdapter();
await adapter.create({
model: "user",
data: { name: "foo", email: "foo@bar.com" },
});
await expect(adapter.create({
model: "user",
data: { name: "foo", email: "foo@bar.com" },
})).rejects.toThrow("user email already exists");
});
test("should be able to compare against a date", async () => {
const adapter = await getAdapter();
const user = await adapter.create({
model: "user",
data: {
name: "foo",
email: "foo@bar.com",
createdAt: new Date().toISOString(),
},
});
expect(await adapter.findOne({
model: "user",
where: [{ field: "createdAt", value: new Date().toISOString() }],
})).toEqual(user);
});
}
//# sourceMappingURL=adapterTest.js.map