From 9b1af8afb0154b767075d87300c639633b77ca69 Mon Sep 17 00:00:00 2001 From: davidercruz Date: Thu, 14 May 2026 12:03:35 +0100 Subject: [PATCH] refactor(persona-quiz): remove guessWhoQuizStep mutation Co-Authored-By: Claude Opus 4.7 (1M context) --- __tests__/schema/onboarding.ts | 180 -------------------------- src/common/schema/guessWhoQuizStep.ts | 15 --- src/integrations/bragi/clients.ts | 34 ----- src/schema/users.ts | 88 ------------- 4 files changed, 317 deletions(-) delete mode 100644 src/common/schema/guessWhoQuizStep.ts diff --git a/__tests__/schema/onboarding.ts b/__tests__/schema/onboarding.ts index d63b2fe6dd..c2e9f0ca01 100644 --- a/__tests__/schema/onboarding.ts +++ b/__tests__/schema/onboarding.ts @@ -23,7 +23,6 @@ jest.mock('../../src/integrations/recswipe/clients', () => ({ }, })); -const mockGuessWhoQuiz = jest.fn(); const mockNextPersonaQuizQuestion = jest.fn(); const mockPersonaQuizReveal = jest.fn(); @@ -33,7 +32,6 @@ jest.mock('../../src/integrations/bragi', () => ({ execute: (fn: () => Promise) => fn(), }, instance: { - guessWhoQuiz: (...args: unknown[]) => mockGuessWhoQuiz(...args), nextPersonaQuizQuestion: (...args: unknown[]) => mockNextPersonaQuizQuestion(...args), personaQuizReveal: (...args: unknown[]) => mockPersonaQuizReveal(...args), @@ -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( diff --git a/src/common/schema/guessWhoQuizStep.ts b/src/common/schema/guessWhoQuizStep.ts deleted file mode 100644 index ced537bc74..0000000000 --- a/src/common/schema/guessWhoQuizStep.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { z } from 'zod'; - -const MAX_TEXT = 500; - -export const guessWhoQuizStepInputSchema = z.object({ - history: z - .array( - z.object({ - question: z.string().min(1).max(MAX_TEXT), - answer: z.string().min(1).max(MAX_TEXT), - }), - ) - .min(5) - .max(20), -}); diff --git a/src/integrations/bragi/clients.ts b/src/integrations/bragi/clients.ts index 6725a76d2d..c80ddbac14 100644 --- a/src/integrations/bragi/clients.ts +++ b/src/integrations/bragi/clients.ts @@ -21,9 +21,6 @@ import { GenerateRecruiterEmailResponse, ExtractedProfileTag, GitHubProfileTagsResponse, - GuessWhoQuizPersona, - GuessWhoQuizQuestion, - GuessWhoQuizResponse, NextPersonaQuizQuestionResponse, OnboardingProfileTagsResponse, PersonaQuizOption, @@ -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, diff --git a/src/schema/users.ts b/src/schema/users.ts index 799a9083bc..dc67076ec3 100644 --- a/src/schema/users.ts +++ b/src/schema/users.ts @@ -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'; @@ -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 @@ -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! @@ -4560,63 +4529,6 @@ export const resolvers: IResolvers = { throw err; } }, - guessWhoQuizStep: async ( - _, - args: z.input, - 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,