Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions evals/000-fundamentals/005-function_calling/TASK.txt
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
Create a demo that demonstrates all the ways to call functions from other functions in Convex.

Start by implementing three internal callee functions in `convex/index.ts`:
- An internal query `calleeQuery` that takes x and y numbers and returns their sum
- An internal mutation `calleeMutation` that takes x and y numbers and returns their difference
- An internal action `calleeAction` that takes x and y numbers and returns their product
- An internal query `calleeQuery` that takes numbers named "x" and "y" and returns their sum
- An internal mutation `calleeMutation` that takes numbers named "x" and "y" and returns their difference
- An internal action `calleeAction` that takes numbers named "x" and "y" and returns their product

Then create two caller functions in `convex/index.ts`:

1. Create a mutation called `callerMutation` that demonstrates:
- Takes no arguments
- Calling the internal query with x=1 and y=2
- Using the result to call the internal mutation with y=2
- Using the result to call the internal mutation with y=2 (keep the parameter names "x" and "y")
- Return the final result

2. Create an action called `callerAction` that demonstrates:
- Takes no arguments
- Calling the internal query with x=1 and y=2
- Using the result to call the internal mutation with y=2
- Using that result to call the internal action with y=2
Expand Down
69 changes: 25 additions & 44 deletions evals/000-fundamentals/005-function_calling/grader.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
import { expect, test } from "vitest";
import {
responseAdminClient,
responseClient,
compareSchema,
compareFunctionSpec,
} from "../../../grader";
import { responseAdminClient, responseClient } from "../../../grader";
import { api } from "./answer/convex/_generated/api";

test("compare schema", async ({ skip }) => {
await compareSchema(skip);
});

test("compare function spec", async ({ skip }) => {
await compareFunctionSpec(skip);
});

test("callerMutation chains calls correctly", async () => {
const result = await responseAdminClient.mutation(
api.index.callerMutation,
Expand All @@ -25,78 +12,70 @@ test("callerMutation chains calls correctly", async () => {
expect(result).toBe(1);

// Test with invalid arguments
let error: any = undefined;
try {
await responseAdminClient.mutation(api.index.callerMutation, { x: 1 });
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.toString()).toContain("ArgumentValidationError");
await expect(
responseAdminClient.mutation(api.index.callerMutation, { x: 1 } as any),
).rejects.toThrow(/ArgumentValidationError/);
});

test("callerAction chains calls correctly", async () => {
const result = await responseAdminClient.action(
api.index.callerAction,
{},
);
const result = await responseAdminClient.action(api.index.callerAction, {});
// calleeQuery(1,2) = 3
// calleeMutation(3,2) = 1
// calleeAction(1,2) = 2
expect(result).toBe(2);

// Test with invalid arguments
let error: any = undefined;
try {
await responseAdminClient.action(api.index.callerAction, { x: 1 });
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.toString()).toContain("ArgumentValidationError");
await expect(
responseAdminClient.action(api.index.callerAction, { x: 1 } as any),
).rejects.toThrow(/ArgumentValidationError/);
});

test("internal functions work correctly", async () => {
// Test calleeQuery
const queryResult = await responseAdminClient.query(
// @ts-ignore
api.index.calleeQuery,
{ x: 5, y: 3 },
{
x: 5,
y: 3,
},
);
expect(queryResult).toBe(8);

// Test calleeMutation

const mutationResult = await responseAdminClient.mutation(
// @ts-ignore
api.index.calleeMutation,
{ x: 5, y: 3 },
);
expect(mutationResult).toBe(2);

// Test calleeAction
// @ts-ignore
const actionResult = await responseAdminClient.action(
// @ts-ignore
api.index.calleeAction,
{ x: 5, y: 3 },
);
expect(actionResult).toBe(15);

// Test argument validation
let error: any = undefined;
try {
await responseAdminClient.query(api.index.calleeQuery, {
await expect(
// @ts-ignore
responseAdminClient.query(api.index.calleeQuery, {
x: "not a number",
y: 3,
});
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.toString()).toContain("ArgumentValidationError");
})
).rejects.toThrow(/ArgumentValidationError/);
});

test("functions are not accessible from wrong client type", async () => {
let error: any = undefined;

// Query should not be callable as mutation
try {
// @ts-ignore
await responseAdminClient.mutation(api.index.calleeQuery, {
x: 1,
y: 2,
Expand All @@ -109,6 +88,7 @@ test("functions are not accessible from wrong client type", async () => {
// Mutation should not be callable as action
error = undefined;
try {
// @ts-ignore
await responseAdminClient.action(api.index.calleeMutation, {
x: 1,
y: 2,
Expand All @@ -121,6 +101,7 @@ test("functions are not accessible from wrong client type", async () => {
// Action should not be callable as query
error = undefined;
try {
// @ts-ignore
await responseAdminClient.query(api.index.calleeAction, { x: 1, y: 2 });
} catch (e) {
error = e;
Expand Down
10 changes: 5 additions & 5 deletions evals/000-fundamentals/006-database_crud/TASK.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,27 @@ export default defineSchema({
Implement the following functions in `convex/public.ts`:

1. Create a mutation `createLocation` that:
- Takes name (string), latitude (number), and longitude (number) as arguments
- Takes arguments named `name` (string), `latitude` (number), and `longitude` (number)
- Inserts a new location into the "locations" table
- Returns the new location's ID

2. Create a query `readLocation` that:
- Takes a location ID as an argument
- Takes a location ID argument named `id`
- Returns either null or the object containing the location's name, latitude, longitude, and its system fields
- Use proper union typing for the return value

3. Create a mutation `updateLocation` that:
- Takes an ID and full location data (name, latitude, longitude)
- Takes arguments named `id`, `name`, `latitude`, and `longitude`
- Replaces the existing location with new data
- Throws an error if the location doesn't exist
- Returns null

4. Create a mutation `patchLocation` that:
- Takes an ID and a new name
- Takes arguments named `id` and `name`
- Updates only the name field
- Returns null

5. Create a mutation `deleteLocation` that:
- Takes a location ID
- Takes a location ID argument named `id`
- Deletes the location from the database, throwing an error if it doesn't exist
- Returns null
30 changes: 6 additions & 24 deletions evals/000-fundamentals/006-database_crud/grader.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
import { expect, test } from "vitest";
import {
responseAdminClient,
responseClient,
compareSchema,
compareFunctionSpec,
} from "../../../grader";
import { responseClient } from "../../../grader";
import { anyApi } from "convex/server";

test("compare schema", async ({ skip }) => {
await compareSchema(skip);
});

test("compare function spec", async ({ skip }) => {
await compareFunctionSpec(skip);
});

test("create and read location", async () => {
// Test successful creation
const locationId = await responseClient.mutation(
Expand All @@ -40,18 +27,13 @@ test("create and read location", async () => {
});

// Test invalid arguments
let error: any = undefined;
try {
await responseClient.mutation(anyApi.public.createLocation, {
await expect(
responseClient.mutation(anyApi.public.createLocation, {
name: "Invalid",
latitude: "not a number",
latitude: "not a number" as unknown as number,
longitude: -122.4194,
});
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.toString()).toContain("ArgumentValidationError");
}),
).rejects.toThrow(/ArgumentValidationError/);
});

test("update location", async () => {
Expand Down
8 changes: 4 additions & 4 deletions evals/000-fundamentals/007-basic_file_storage/TASK.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,24 @@ export default defineSchema({
- Returns a string URL for file upload

2. Create a mutation `finishUpload`:
- Takes a storage ID as an argument
- Takes a storage ID argument named `storageId`
- Inserts a new record in the "files" table with the storage ID
- Returns null

3. Create a query `getFileUrl`:
- Takes a file ID as an argument
- Takes a file ID argument named `fileId`
- Retrieves the file record from the database, throwing an error if not found
- Gets the download URL for the storage ID associated with the file.
- Throws an error if the storage entry is not found
- Returns the URL as a string

4. Create a query `getFileMetadata`:
- Takes a file ID as an argument
- Takes a file ID argument named `fileId`
- Retrieves the file record and returns all of its system metadata
- Throws an error if the file is not found

5. Create a mutation `deleteFile`:
- Takes a file ID as an argument
- Takes a file ID argument named `fileId`
- Deletes both the storage object and database record
- Throws an error if the file is not found

Expand Down
61 changes: 49 additions & 12 deletions evals/000-fundamentals/007-basic_file_storage/grader.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,54 @@
import { expect, test } from "vitest";
import {
responseAdminClient,
responseClient,
compareSchema,
compareFunctionSpec,
} from "../../../grader";
import { api } from "./answer/convex/_generated/api";
import { responseClient } from "../../../grader";
import { anyApi } from "convex/server";

test("compare schema", async ({ skip }) => {
await compareSchema(skip);
type Brand<T, B extends string> = T & { __brand: B };
type FilesId = Brand<string, "files">;
type StorageId = Brand<string, "_storage">;

test("generate upload URL returns a string", async () => {
const url: unknown = await responseClient.mutation(
anyApi.index.generateUploadUrl,
{},
);
expect(typeof url).toBe("string");
if (typeof url === "string") expect(url.length).toBeGreaterThan(0);
});

test("finishUpload stores file record", async () => {
const url: unknown = await responseClient.mutation(
anyApi.index.generateUploadUrl,
{},
);
expect(url).toBeTypeOf("string");
// Simulate storage by creating a dummy storage id through upload flow is out of scope; rely on API shape
await expect(
responseClient.mutation(anyApi.index.finishUpload, {
storageId: "storage:fake" as unknown as StorageId,
}),
).rejects.toBeDefined();
});

test("getFileUrl throws for missing file", async () => {
await expect(
responseClient.query(anyApi.index.getFileUrl, {
fileId: "files:missing" as unknown as FilesId,
}),
).rejects.toBeDefined();
});

test("getFileMetadata throws for missing file", async () => {
await expect(
responseClient.query(anyApi.index.getFileMetadata, {
fileId: "files:missing" as unknown as FilesId,
}),
).rejects.toBeDefined();
});

test("compare function spec", async ({ skip }) => {
// TODO: Claude Sonnet 3.5 *really* wants to output the files at `convex/files.ts`.
await compareFunctionSpec(skip);
test("deleteFile throws for missing file", async () => {
await expect(
responseClient.mutation(anyApi.index.deleteFile, {
fileId: "files:missing" as unknown as FilesId,
}),
).rejects.toBeDefined();
});
6 changes: 3 additions & 3 deletions evals/000-fundamentals/008-helper_fns/TASK.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default defineSchema({
});
```

2. Create a helper function `getItemData` in `convex/index.ts` that takes in an item ID, fetches it from the database, and returns a document like:
2. Create a helper function `getItemData` in `convex/index.ts` that takes an item ID and fetches it from the database, returning a document like:

```ts
{
Expand All @@ -30,12 +30,12 @@ Return null if item not found, otherwise returns the formatted data
3. Create more functions in `convex/index.ts`:

a. Create a query `getItem` that:
- Takes an item ID as an argument
- Takes an item ID argument with the name "itemId"
- Uses the shared helper function to retrieve and transform the item from the database
- Throws an error if item not found

b. Create a mutation `updateItem` that:
- Takes an item ID and new quantity as arguments
- Takes an item ID argument with the name "itemId" and a new quantity argument with the name "quantity"
- Updates the item's quantity and lastModified timestamp
- Retrieves the item via the shared helper function
- Throws an error if item not found
Expand Down
Loading