Add features & update project
This commit is contained in:
@@ -34,6 +34,13 @@ const spoonInput = {
|
||||
productionRefStrategy: 'default_branch' as const,
|
||||
};
|
||||
|
||||
const githubSpoonInput = {
|
||||
...spoonInput,
|
||||
provider: 'github' as const,
|
||||
upstreamUrl: 'https://github.com/upstream/editor',
|
||||
forkUrl: 'https://github.com/team/editor-spoon',
|
||||
};
|
||||
|
||||
const createAgentJob = async (
|
||||
t: ReturnType<typeof convexTest>,
|
||||
args: {
|
||||
@@ -114,6 +121,54 @@ describe('convex-test harness', () => {
|
||||
expect(spoons[0]?.ownerId).toBe(userId);
|
||||
});
|
||||
|
||||
test('lists effective drift after ignored upstream changes', async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const ownerId = await createUser(t, 'owner@example.com');
|
||||
const spoonId = await authed(t, ownerId).mutation(
|
||||
api.spoons.createManual,
|
||||
githubSpoonInput,
|
||||
);
|
||||
await t.mutation(async (ctx) => {
|
||||
const now = Date.now();
|
||||
await ctx.db.insert('spoonRepositoryStates', {
|
||||
spoonId,
|
||||
ownerId: ownerId as Id<'users'>,
|
||||
upstreamFullName: 'upstream/editor',
|
||||
forkFullName: 'team/editor-spoon',
|
||||
upstreamDefaultBranch: 'main',
|
||||
forkDefaultBranch: 'main',
|
||||
upstreamHeadSha: 'upstream-head',
|
||||
forkHeadSha: 'fork-head',
|
||||
upstreamAheadBy: 2,
|
||||
forkAheadBy: 1,
|
||||
status: 'diverged',
|
||||
openForkPullRequestCount: 0,
|
||||
openUpstreamPullRequestCount: 0,
|
||||
refreshedAt: now,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
await ctx.db.insert('ignoredUpstreamChanges', {
|
||||
spoonId,
|
||||
ownerId: ownerId as Id<'users'>,
|
||||
upstreamTo: 'upstream-head',
|
||||
commitShas: ['abc123'],
|
||||
reason: 'irrelevant',
|
||||
decidedBy: 'user',
|
||||
createdAt: now,
|
||||
});
|
||||
});
|
||||
|
||||
const spoons = await authed(t, ownerId).query(
|
||||
api.spoons.listMineWithState,
|
||||
{},
|
||||
);
|
||||
|
||||
expect(spoons[0]?.rawUpstreamAheadBy).toBe(2);
|
||||
expect(spoons[0]?.effectiveUpstreamAheadBy).toBe(1);
|
||||
expect(spoons[0]?.ignoredUpstreamCount).toBe(1);
|
||||
});
|
||||
|
||||
test('does not allow reading another user’s Spoon', async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const ownerId = await createUser(t, 'owner@example.com');
|
||||
@@ -128,6 +183,38 @@ describe('convex-test harness', () => {
|
||||
).rejects.toThrow('Spoon not found.');
|
||||
});
|
||||
|
||||
test('thread notes are completed when no workspace handles them', async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const ownerId = (await createUser(t, 'owner@example.com')) as Id<'users'>;
|
||||
const spoonId = await authed(t, ownerId).mutation(
|
||||
api.spoons.createManual,
|
||||
githubSpoonInput,
|
||||
);
|
||||
const threadId = await t.mutation(async (ctx) => {
|
||||
return await ctx.db.insert('threads', {
|
||||
ownerId,
|
||||
spoonId,
|
||||
title: 'Manual note',
|
||||
source: 'user_request',
|
||||
status: 'open',
|
||||
priority: 'normal',
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
});
|
||||
|
||||
const messageId = await authed(t, ownerId).mutation(
|
||||
api.threads.appendUserMessage,
|
||||
{
|
||||
threadId,
|
||||
content: 'extra context',
|
||||
},
|
||||
);
|
||||
const message = await t.run(async (ctx) => await ctx.db.get(messageId));
|
||||
|
||||
expect(message?.status).toBe('completed');
|
||||
});
|
||||
|
||||
test('requires Spoon ownership for agent requests', async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const ownerId = await createUser(t, 'owner@example.com');
|
||||
@@ -211,4 +298,59 @@ describe('convex-test harness', () => {
|
||||
authed(t, otherId).mutation(api.agentJobs.deleteWorkspace, { jobId }),
|
||||
).rejects.toThrow('Agent job not found.');
|
||||
});
|
||||
|
||||
test('queues a new thread job after the previous job is terminal', async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const ownerId = (await createUser(t, 'owner@example.com')) as Id<'users'>;
|
||||
const spoonId = await authed(t, ownerId).mutation(
|
||||
api.spoons.createManual,
|
||||
githubSpoonInput,
|
||||
);
|
||||
await t.mutation(async (ctx) => {
|
||||
await ctx.db.insert('aiProviderProfiles', {
|
||||
ownerId,
|
||||
name: 'Test provider',
|
||||
provider: 'openai',
|
||||
authType: 'none',
|
||||
defaultModel: 'gpt-5.1-codex',
|
||||
modelOptions: ['gpt-5.1-codex'],
|
||||
reasoningEffort: 'medium',
|
||||
enabled: true,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
});
|
||||
const threadId = await t.mutation(async (ctx) => {
|
||||
return await ctx.db.insert('threads', {
|
||||
ownerId,
|
||||
spoonId,
|
||||
title: 'Retryable thread',
|
||||
summary: 'try again',
|
||||
source: 'user_request',
|
||||
status: 'failed',
|
||||
priority: 'normal',
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
});
|
||||
const failedJobId = await createAgentJob(t, {
|
||||
ownerId,
|
||||
spoonId,
|
||||
status: 'failed',
|
||||
workspaceStatus: 'failed',
|
||||
});
|
||||
await t.mutation(async (ctx) => {
|
||||
await ctx.db.patch(threadId, { latestAgentJobId: failedJobId });
|
||||
});
|
||||
|
||||
const newJobId = await authed(t, ownerId).mutation(
|
||||
api.agentJobs.createForThread,
|
||||
{
|
||||
threadId,
|
||||
jobType: 'user_change',
|
||||
},
|
||||
);
|
||||
|
||||
expect(newJobId).not.toBe(failedJobId);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user