OuiOuiCroissant
OuiOuiCroissant2mo ago

Convex test does not validate schemas

This is a problem I've been having for a while now, and It's probably time to fix it. this code runs perfectly fine:
test("create + getByAuthId + get", async () => {
const t = convexTest(schema);
// 1) create a new user via your create mutation
const newUser: User = {
authId: AuthId("auth-1"),
invalid: "invalid",
};
const userId = await t.mutation(internal.users.create, newUser);
// 2) fetch by authId
const byAuth = await t.query(internal.users.getByAuthId, { authId: AuthId("auth-1") });
expect(byAuth).toMatchObject(Option.some(newUser));

// 3) fetch by generated ID
const byId = await t.query(internal.users.get, { userId });
expect(byId).toMatchObject(Option.some(newUser));
});
test("create + getByAuthId + get", async () => {
const t = convexTest(schema);
// 1) create a new user via your create mutation
const newUser: User = {
authId: AuthId("auth-1"),
invalid: "invalid",
};
const userId = await t.mutation(internal.users.create, newUser);
// 2) fetch by authId
const byAuth = await t.query(internal.users.getByAuthId, { authId: AuthId("auth-1") });
expect(byAuth).toMatchObject(Option.some(newUser));

// 3) fetch by generated ID
const byId = await t.query(internal.users.get, { userId });
expect(byId).toMatchObject(Option.some(newUser));
});
with the following schema:
export const User = v.union(
v.object({
...BaseUser.fields,
...AdminOnly.fields
}),
v.object({
...BaseUser.fields,
...EmployeeOnly.fields
}),
);
export type User = typeof User.type
// =============================================================================
// USER TABLE DEFINITION
// =============================================================================

export const usersTable = defineTable(User)
.index("by_auth_id", ["authId"])
.index("by_role", ["role"])
.index("by_deleted_at", ["deletedAt"]);


export default defineSchema({
//...
users: usersTable,
});
export const User = v.union(
v.object({
...BaseUser.fields,
...AdminOnly.fields
}),
v.object({
...BaseUser.fields,
...EmployeeOnly.fields
}),
);
export type User = typeof User.type
// =============================================================================
// USER TABLE DEFINITION
// =============================================================================

export const usersTable = defineTable(User)
.index("by_auth_id", ["authId"])
.index("by_role", ["role"])
.index("by_deleted_at", ["deletedAt"]);


export default defineSchema({
//...
users: usersTable,
});
Any Ideas on exactly why this is happening?
6 Replies
Convex Bot
Convex Bot2mo ago
Thanks for posting in <#1088161997662724167>. Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets. - Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.) - Use search.convex.dev to search Docs, Stack, and Discord all at once. - Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI. - Avoid tagging staff unless specifically instructed. Thank you!
sshader
sshader2mo ago
Can you share more of what the mutation that should be hitting the error + the full definition of the Users table looks like? From this test, I'd expect schema validation to generally work, so I'm wondering what specifically might be different in your case
GitHub
convex-test/convex/schemaValidation.test.ts at 7bc871107d30067fb131...
Testing harness for pure-JS Convex tests. Contribute to get-convex/convex-test development by creating an account on GitHub.
OuiOuiCroissant
OuiOuiCroissantOP2mo ago
export const testInsert = internalMutation({
args: User,
handler: async (ctx, args) =>
ctx.db.insert("users", args),
});
export const testInsert = internalMutation({
args: User,
handler: async (ctx, args) =>
ctx.db.insert("users", args),
});
this is as simple as it gets but still fails
test("create + getByAuthId + get", async () => {
const t = convexTest(schema);
// 1) create a new user via your create mutation
const newUser: User = {
authId: AuthId("auth-1"),
invalid: "invalid",
};
const userId = await t.mutation(internal.users.testInsert, newUser);
// 2) fetch by authId
const byAuth = await t.query(internal.users.getByAuthId, { authId: AuthId("auth-1") });
expect(byAuth).toMatchObject(Option.some(newUser));

// 3) fetch by generated ID
const byId = await t.query(internal.users.get, { userId });
expect(byId).toMatchObject(Option.some(newUser));
});
test("create + getByAuthId + get", async () => {
const t = convexTest(schema);
// 1) create a new user via your create mutation
const newUser: User = {
authId: AuthId("auth-1"),
invalid: "invalid",
};
const userId = await t.mutation(internal.users.testInsert, newUser);
// 2) fetch by authId
const byAuth = await t.query(internal.users.getByAuthId, { authId: AuthId("auth-1") });
expect(byAuth).toMatchObject(Option.some(newUser));

// 3) fetch by generated ID
const byId = await t.query(internal.users.get, { userId });
expect(byId).toMatchObject(Option.some(newUser));
});
export type AuthId = Brand.Branded<string, "AuthId">;
export const AuthId = Brand.nominal<AuthId>()

const BaseUser = v.object({
authId: v.string() as Validator<AuthId>,
firstName: v.string(),
lastName: v.string(),
deletedAt: v.optional(v.number()),
currentLocationId: v.optional(v.id("locations")),
});
export type BaseUser = typeof BaseUser.type;


const AdminOnly = v.object({
role: v.literal("admin")
})

const EmployeeOnly = v.object({
role: v.literal("employee")
})
export const User = v.union(
v.object({
...BaseUser.fields,
...AdminOnly.fields
}),
v.object({
...BaseUser.fields,
...EmployeeOnly.fields
}),
);
export type User = typeof User.type

export const usersTable = defineTable(User)
.index("by_auth_id", ["authId"])
.index("by_role", ["role"])
.index("by_deleted_at", ["deletedAt"]);
export type AuthId = Brand.Branded<string, "AuthId">;
export const AuthId = Brand.nominal<AuthId>()

const BaseUser = v.object({
authId: v.string() as Validator<AuthId>,
firstName: v.string(),
lastName: v.string(),
deletedAt: v.optional(v.number()),
currentLocationId: v.optional(v.id("locations")),
});
export type BaseUser = typeof BaseUser.type;


const AdminOnly = v.object({
role: v.literal("admin")
})

const EmployeeOnly = v.object({
role: v.literal("employee")
})
export const User = v.union(
v.object({
...BaseUser.fields,
...AdminOnly.fields
}),
v.object({
...BaseUser.fields,
...EmployeeOnly.fields
}),
);
export type User = typeof User.type

export const usersTable = defineTable(User)
.index("by_auth_id", ["authId"])
.index("by_role", ["role"])
.index("by_deleted_at", ["deletedAt"]);
I have an inkling this is union related just added union schema test to convex-test and they pass, same setup odd
sshader
sshader2mo ago
Hmm I cannot reproduce this (copied your exact schema minus the branded strings + exact mutation into the convex-test repo). Mind sharing the version of convex-test + convex you're using? Also just to double check the obvious thing -- you don't have schemaValidation: true set in your schema + the name of the table in the defineSchema call is "users" and not something else?
OuiOuiCroissant
OuiOuiCroissantOP2mo ago
sorry for not replying, updating and some cleaning up did the trick
sshader
sshader2mo ago
Glad that seems to have fixed it!

Did you find this page helpful?