Skip to content
Merged
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
180 changes: 0 additions & 180 deletions __tests__/schema/onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ jest.mock('../../src/integrations/recswipe/clients', () => ({
},
}));

const mockGuessWhoQuiz = jest.fn();
const mockNextPersonaQuizQuestion = jest.fn();
const mockPersonaQuizReveal = jest.fn();

Expand All @@ -33,7 +32,6 @@ jest.mock('../../src/integrations/bragi', () => ({
execute: (fn: () => Promise<unknown>) => fn(),
},
instance: {
guessWhoQuiz: (...args: unknown[]) => mockGuessWhoQuiz(...args),
nextPersonaQuizQuestion: (...args: unknown[]) =>
mockNextPersonaQuizQuestion(...args),
personaQuizReveal: (...args: unknown[]) => mockPersonaQuizReveal(...args),
Expand Down Expand Up @@ -404,184 +402,6 @@ describe('mutation onboardingRecommendTags', () => {
});
});

describe('mutation guessWhoQuizStep', () => {
const MUTATION = /* GraphQL */ `
mutation GuessWhoQuizStep($history: [GuessWhoQuizQAInput!]!) {
guessWhoQuizStep(history: $history) {
nextQuestion {
question
options
}
finalPersona {
name
description
tags
}
}
}
`;

const fiveTurns = Array.from({ length: 5 }, (_, i) => ({
question: `Q${i + 1}`,
answer: `A${i + 1}`,
}));

it('should require authentication', () =>
testMutationErrorCode(
client,
{ mutation: MUTATION, variables: { history: fiveTurns } },
'UNAUTHENTICATED',
));

it('should reject input with fewer than 5 history pairs', async () => {
loggedUser = '1';
await testMutationErrorCode(
client,
{ mutation: MUTATION, variables: { history: fiveTurns.slice(0, 3) } },
'ZOD_VALIDATION_ERROR',
);
expect(mockGuessWhoQuiz).not.toHaveBeenCalled();
});

it('should return next question without calling recswipe', async () => {
loggedUser = '1';
mockGuessWhoQuiz.mockResolvedValueOnce({
id: 'op-1',
result: {
case: 'nextQuestion',
value: {
question: 'How do you feel about feature flags?',
options: ['ship it', 'sparingly', 'avoid', 'never used'],
},
},
});

const res = await client.mutate(MUTATION, {
variables: { history: fiveTurns },
});

expect(res.errors).toBeFalsy();
expect(res.data).toEqual({
guessWhoQuizStep: {
nextQuestion: {
question: 'How do you feel about feature flags?',
options: ['ship it', 'sparingly', 'avoid', 'never used'],
},
finalPersona: null,
},
});
expect(extractTagsMock).not.toHaveBeenCalled();
});

it('should call recswipe.extractTags and return persona with tags on final', async () => {
loggedUser = '1';
mockGuessWhoQuiz.mockResolvedValueOnce({
id: 'op-2',
result: {
case: 'finalPersona',
value: {
name: 'Pragmatic Backend Architect',
description:
'You wrangle systems for a living and stay quietly suspicious of every shiny new abstraction.',
},
},
});
extractTagsMock.mockResolvedValueOnce({
tags: ['backend', 'systems', 'go'],
});

const res = await client.mutate(MUTATION, {
variables: { history: fiveTurns },
});

expect(res.errors).toBeFalsy();
expect(res.data).toEqual({
guessWhoQuizStep: {
nextQuestion: null,
finalPersona: {
name: 'Pragmatic Backend Architect',
description:
'You wrangle systems for a living and stay quietly suspicious of every shiny new abstraction.',
tags: ['backend', 'systems', 'go'],
},
},
});
expect(extractTagsMock).toHaveBeenCalledWith('1', {
prompt:
'You wrangle systems for a living and stay quietly suspicious of every shiny new abstraction.',
});
});

it('should default missing tags to empty list', async () => {
loggedUser = '1';
mockGuessWhoQuiz.mockResolvedValueOnce({
id: 'op-3',
result: {
case: 'finalPersona',
value: {
name: 'Curious Developer',
description: 'You poke at things.',
},
},
});
extractTagsMock.mockResolvedValueOnce({
tags: undefined as unknown as string[],
});

const res = await client.mutate(MUTATION, {
variables: { history: fiveTurns },
});

expect(res.errors).toBeFalsy();
expect(res.data).toEqual({
guessWhoQuizStep: {
nextQuestion: null,
finalPersona: {
name: 'Curious Developer',
description: 'You poke at things.',
tags: [],
},
},
});
});

it('should error when bragi returns neither branch', async () => {
loggedUser = '1';
mockGuessWhoQuiz.mockResolvedValueOnce({
id: 'op-4',
result: { case: undefined },
});

const res = await client.mutate(MUTATION, {
variables: { history: fiveTurns },
});

expect(res.errors?.length).toBeGreaterThan(0);
expect(extractTagsMock).not.toHaveBeenCalled();
});

it('should surface an UNEXPECTED error when recswipe extractTags fails', async () => {
loggedUser = '1';
mockGuessWhoQuiz.mockResolvedValueOnce({
id: 'op-5',
result: {
case: 'finalPersona',
value: { name: 'X', description: 'Y' },
},
});
extractTagsMock.mockRejectedValueOnce(
new HttpError('http://recswipe.local:8000/api/extract-tags', 500, 'boom'),
);

const res = await client.mutate(MUTATION, {
variables: { history: fiveTurns },
});

expect(res.errors?.length).toBeGreaterThan(0);
expect(res.errors?.[0].extensions?.code).toBe('UNEXPECTED');
});
});

describe('mutation personaQuizNextQuestion', () => {
const MUTATION = /* GraphQL */ `
mutation PersonaQuizNextQuestion(
Expand Down
15 changes: 0 additions & 15 deletions src/common/schema/guessWhoQuizStep.ts

This file was deleted.

34 changes: 0 additions & 34 deletions src/integrations/bragi/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ import {
GenerateRecruiterEmailResponse,
ExtractedProfileTag,
GitHubProfileTagsResponse,
GuessWhoQuizPersona,
GuessWhoQuizQuestion,
GuessWhoQuizResponse,
NextPersonaQuizQuestionResponse,
OnboardingProfileTagsResponse,
PersonaQuizOption,
Expand Down Expand Up @@ -183,37 +180,6 @@ export const getBragiClient = (
new ExtractedProfileTag({ name: 'ai', confidence: 0.7 }),
],
}),
guessWhoQuiz: async ({
history,
}: {
history?: { question: string; answer: string }[];
}) =>
new GuessWhoQuizResponse({
id: 'mock-id',
result:
(history?.length ?? 0) >= 8
? {
case: 'finalPersona',
value: new GuessWhoQuizPersona({
name: 'Pragmatic Backend Architect',
description:
'You wrangle systems for a living and stay quietly suspicious of every shiny new abstraction.',
}),
}
: {
case: 'nextQuestion',
value: new GuessWhoQuizQuestion({
question:
'How do you feel about feature flags in production?',
options: [
'Ship it gated, always',
'Use them sparingly',
'Avoid them, prefer canaries',
'Never used one in anger',
],
}),
},
}),
nextPersonaQuizQuestion: async ({
askedCount,
maxQuestions,
Expand Down
88 changes: 0 additions & 88 deletions src/schema/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Code, ConnectError } from '@connectrpc/connect';
import { getBragiClient } from '../integrations/bragi';
import { Keyword, KeywordStatus } from '../entity/Keyword';
import type { z } from 'zod';
import { guessWhoQuizStepInputSchema } from '../common/schema/guessWhoQuizStep';
import { onboardingDiscoverPostsInputSchema } from '../common/schema/onboardingDiscoverPosts';
import { onboardingExtractTagsInputSchema } from '../common/schema/onboardingExtractTags';
import { onboardingProfileTagsInputSchema } from '../common/schema/onboardingProfileTags';
Expand Down Expand Up @@ -1841,15 +1840,6 @@ export const typeDefs = /* GraphQL */ `
n: Int
): OnboardingRecommendTagsResult! @auth

"""
Send the current Guess Who quiz Q&A history to bragi. Returns either the
next clarifying question or the final persona (with tags extracted from the
persona description via recswipe). Stateless — caller resends history each
turn.
"""
guessWhoQuizStep(history: [GuessWhoQuizQAInput!]!): GuessWhoQuizStepResult!
@auth

"""
Akinator-style persona quiz: ask bragi for the next guess based on prior
Q&A. daily-api fetches NMF candidate_topics from recswipe and passes them
Expand Down Expand Up @@ -1905,27 +1895,6 @@ export const typeDefs = /* GraphQL */ `
tags: [String!]!
}

input GuessWhoQuizQAInput {
question: String!
answer: String!
}

type GuessWhoQuizQuestion {
question: String!
options: [String!]!
}

type GuessWhoQuizPersona {
name: String!
description: String!
tags: [String!]!
}

type GuessWhoQuizStepResult {
nextQuestion: GuessWhoQuizQuestion
finalPersona: GuessWhoQuizPersona
}

input PersonaQuizQAInput {
questionId: String!
question: String!
Expand Down Expand Up @@ -4560,63 +4529,6 @@ export const resolvers: IResolvers<unknown, BaseContext> = {
throw err;
}
},
guessWhoQuizStep: async (
_,
args: z.input<typeof guessWhoQuizStepInputSchema>,
ctx: AuthContext,
) => {
const parsed = guessWhoQuizStepInputSchema.parse(args);

try {
const client = getBragiClient();
const bragiResp = await client.garmr.execute(() =>
client.instance.guessWhoQuiz({
history: parsed.history,
application: 'webapp',
}),
);

const { result } = bragiResp;
if (result.case === 'nextQuestion') {
return {
nextQuestion: {
question: result.value.question,
options: [...result.value.options],
},
finalPersona: null,
};
}
if (result.case === 'finalPersona') {
const persona = result.value;
const tagData = await recswipeClient.extractTags(ctx.userId, {
prompt: persona.description,
});
return {
nextQuestion: null,
finalPersona: {
name: persona.name,
description: persona.description,
tags: tagData.tags ?? [],
},
};
}
throw new ServiceError({
message:
'Bragi guessWhoQuiz returned neither nextQuestion nor finalPersona',
statusCode: 502,
});
} catch (err) {
if (err instanceof HttpError) {
throw new ServiceError({
message: 'guessWhoQuizStep request failed',
data: err.response,
statusCode: err.statusCode,
});
}

throw err;
}
},
personaQuizNextQuestion: async (
_,
args: z.input<typeof personaQuizNextQuestionInputSchema>,
Expand Down
Loading