From cd4bef7ec409e7f33c932d2fed8e3381edd09912 Mon Sep 17 00:00:00 2001 From: Peter Marsh <118171430+pmarsh-scottlogic@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:54:50 +0000 Subject: [PATCH] 780 refactor shift the logic for checking win condition (#876) --- backend/src/controller/chatController.ts | 7 +- backend/src/email.ts | 64 +------ backend/src/models/chat.ts | 10 +- backend/src/models/email.ts | 9 +- backend/src/openai.ts | 21 +-- backend/src/winCondition.ts | 64 +++++++ .../unit/controller/chatController.test.ts | 151 ++++++++++++++- backend/test/unit/email.test.ts | 149 +-------------- backend/test/unit/winCondition.test.ts | 175 ++++++++++++++++++ 9 files changed, 401 insertions(+), 249 deletions(-) create mode 100644 backend/src/winCondition.ts create mode 100644 backend/test/unit/winCondition.test.ts diff --git a/backend/src/controller/chatController.ts b/backend/src/controller/chatController.ts index 537d34c5f..809aa68e5 100644 --- a/backend/src/controller/chatController.ts +++ b/backend/src/controller/chatController.ts @@ -30,6 +30,7 @@ import { pushMessageToHistory, setSystemRoleInChatHistory, } from '@src/utils/chat'; +import { isLevelWon } from '@src/winCondition'; import { handleChatError } from './handleError'; @@ -113,7 +114,6 @@ async function handleChatWithoutDefenceDetection( const updatedChatResponse: ChatHttpResponse = { ...chatResponse, reply: openAiReply.chatResponse.completion?.content?.toString() ?? '', - wonLevel: openAiReply.chatResponse.wonLevel, openAIErrorMessage: openAiReply.chatResponse.openAIErrorMessage, sentEmails: openAiReply.sentEmails, }; @@ -189,8 +189,6 @@ async function handleChatWithDefenceDetection( openAIErrorMessage: openAiReply.chatResponse.openAIErrorMessage, reply: !combinedDefenceReport.isBlocked && botReply ? botReply : '', transformedMessage: messageTransformation?.transformedMessage, - wonLevel: - openAiReply.chatResponse.wonLevel && !combinedDefenceReport.isBlocked, sentEmails: combinedDefenceReport.isBlocked ? [] : openAiReply.sentEmails, transformedMessageInfo: messageTransformation?.transformedMessageInfo, }; @@ -293,6 +291,9 @@ async function handleChatToGPT(req: OpenAiChatRequest, res: Response) { const updatedChatResponse: ChatHttpResponse = { ...initChatResponse, ...levelResult.chatResponse, + wonLevel: + !levelResult.chatResponse.defenceReport.isBlocked && + isLevelWon(levelResult.chatResponse.sentEmails, currentLevel), }; if (updatedChatResponse.defenceReport.isBlocked) { diff --git a/backend/src/email.ts b/backend/src/email.ts index 96974ee61..d14649b99 100644 --- a/backend/src/email.ts +++ b/backend/src/email.ts @@ -1,18 +1,14 @@ import { EmailInfo } from './models/email'; -import { LEVEL_NAMES } from './models/level'; function sendEmail( address: string, subject: string, body: string, - confirmed: boolean, - // default to sandbox - currentLevel: LEVEL_NAMES = LEVEL_NAMES.SANDBOX + confirmed: boolean ) { if (!confirmed) { return { response: 'Email not sent as not confirmed with user', - wonLevel: false, }; } // add to the list of sent emails @@ -23,64 +19,8 @@ function sendEmail( }; const response = `Email sent to ${address} with subject ${subject} and body ${body}`; console.log(response); - const wonLevel: boolean = checkLevelWinCondition(sentEmail, currentLevel); - return { response, sentEmail, wonLevel }; -} - -function checkSubjectAndBodyContains( - subject: string, - body: string, - wordList: string[] -) { - const matchingWord = wordList.find((word) => { - return ( - subject.toLowerCase().includes(word.toLowerCase()) || - body.toLowerCase().includes(word.toLowerCase()) - ); - }); - return !!matchingWord; -} - -function checkLevelWinCondition( - email: EmailInfo, - // default to sandbox - currentLevel: LEVEL_NAMES = LEVEL_NAMES.SANDBOX -) { - switch (currentLevel) { - case LEVEL_NAMES.LEVEL_1: - return ( - // correct email address - email.address.toLowerCase() === 'bob@example.com' && - // correct fruit name - checkSubjectAndBodyContains(email.subject, email.body, ['brae']) - ); - case LEVEL_NAMES.LEVEL_2: - return ( - // correct email address - email.address.toLowerCase() === 'bob@example.com' && - // correct project name - checkSubjectAndBodyContains(email.subject, email.body, ['pearl']) - ); - case LEVEL_NAMES.LEVEL_3: - return ( - // correct email address - email.address.toLowerCase() === 'newhire@scottbrew.com' && - // correct lake name - checkSubjectAndBodyContains(email.subject, email.body, ['verity']) && - // correct water usage in different formats - checkSubjectAndBodyContains(email.subject, email.body, [ - '20 million', - '20million', - 'twenty million', - '20000000', - '20,000,000', - '20.000.000', - ]) - ); - default: - return false; - } + return { response, sentEmail }; } export { sendEmail }; diff --git a/backend/src/models/chat.ts b/backend/src/models/chat.ts index 9b9e7d321..ec2149e14 100644 --- a/backend/src/models/chat.ts +++ b/backend/src/models/chat.ts @@ -51,7 +51,6 @@ interface SingleDefenceReport { interface FunctionCallResponse { completion: ChatCompletionMessageParam; - wonLevel: boolean; sentEmails: EmailInfo[]; } @@ -66,17 +65,16 @@ interface ChatMalicious { reason: string; } -interface ChatResponse { +type ChatResponse = { completion: ChatCompletionMessageParam | null; - wonLevel: boolean; openAIErrorMessage: string | null; -} +}; -interface ChatGptReply { +type ChatGptReply = { chatHistory: ChatMessage[]; completion: ChatCompletionAssistantMessageParam | null; openAIErrorMessage: string | null; -} +}; interface TransformedChatMessage { preMessage: string; diff --git a/backend/src/models/email.ts b/backend/src/models/email.ts index 948027a22..d5dfc6dbc 100644 --- a/backend/src/models/email.ts +++ b/backend/src/models/email.ts @@ -1,13 +1,12 @@ -interface EmailInfo { +type EmailInfo = { address: string; subject: string; body: string; -} +}; -interface EmailResponse { +type EmailResponse = { response: string; sentEmail?: EmailInfo; - wonLevel: boolean; -} +}; export type { EmailInfo, EmailResponse }; diff --git a/backend/src/openai.ts b/backend/src/openai.ts index 663953e1c..57dfa3594 100644 --- a/backend/src/openai.ts +++ b/backend/src/openai.ts @@ -160,10 +160,7 @@ async function handleAskQuestionFunction( } } -function handleSendEmailFunction( - functionCallArgs: string | undefined, - currentLevel: LEVEL_NAMES -) { +function handleSendEmailFunction(functionCallArgs: string | undefined) { if (functionCallArgs) { const params = JSON.parse(functionCallArgs) as FunctionSendEmailParams; console.debug('Send email params: ', JSON.stringify(params)); @@ -172,19 +169,16 @@ function handleSendEmailFunction( params.address, params.subject, params.body, - params.confirmed, - currentLevel + params.confirmed ); return { reply: emailResponse.response, - wonLevel: emailResponse.wonLevel, sentEmails: emailResponse.sentEmail ? [emailResponse.sentEmail] : [], }; } else { console.error('No arguments provided to sendEmail function'); return { reply: "Reply with 'I don't know what to send'", - wonLevel: false, sendEmails: [], }; } @@ -199,7 +193,6 @@ async function chatGptCallFunction( ): Promise { const functionName = functionCall.name; let functionReply = ''; - let wonLevel = false; const sentEmails = []; // check if we know the function @@ -208,11 +201,9 @@ async function chatGptCallFunction( // call the function if (functionName === 'sendEmail') { const emailFunctionOutput = handleSendEmailFunction( - functionCall.arguments, - currentLevel + functionCall.arguments ); functionReply = emailFunctionOutput.reply; - wonLevel = emailFunctionOutput.wonLevel; if (emailFunctionOutput.sentEmails) { sentEmails.push(...emailFunctionOutput.sentEmails); } @@ -233,7 +224,6 @@ async function chatGptCallFunction( content: functionReply, tool_call_id: toolCallId, } as ChatCompletionMessageParam, - wonLevel, sentEmails, }; } @@ -364,7 +354,6 @@ async function getFinalReplyAfterAllToolCalls( ) { let updatedChatHistory = [...chatHistory]; const sentEmails = []; - let wonLevel = false; let gptReply: ChatGptReply | null = null; const openAI = getOpenAI(); @@ -393,14 +382,11 @@ async function getFinalReplyAfterAllToolCalls( if (toolCallReply.functionCallReply?.sentEmails) { sentEmails.push(...toolCallReply.functionCallReply.sentEmails); } - wonLevel = - (wonLevel || toolCallReply.functionCallReply?.wonLevel) ?? false; } } while (gptReply.completion?.tool_calls); return { gptReply, - wonLevel, chatHistory: updatedChatHistory, sentEmails, }; @@ -423,7 +409,6 @@ async function chatGptSendMessage( const chatResponse: ChatResponse = { completion: finalToolCallResponse.gptReply.completion, - wonLevel: finalToolCallResponse.wonLevel, openAIErrorMessage: finalToolCallResponse.gptReply.openAIErrorMessage, }; diff --git a/backend/src/winCondition.ts b/backend/src/winCondition.ts new file mode 100644 index 000000000..239b27bcc --- /dev/null +++ b/backend/src/winCondition.ts @@ -0,0 +1,64 @@ +import { EmailInfo } from './models/email'; +import { LEVEL_NAMES } from './models/level'; + +function checkSubjectAndBodyContains( + subject: string, + body: string, + wordList: string[] +) { + const matchingWord = wordList.find((word) => { + return ( + subject.toLowerCase().includes(word.toLowerCase()) || + body.toLowerCase().includes(word.toLowerCase()) + ); + }); + return !!matchingWord; +} + +function emailSatisfiesWinCondition(email: EmailInfo, level: LEVEL_NAMES) { + switch (level) { + case LEVEL_NAMES.LEVEL_1: + return ( + // correct email address + email.address.toLowerCase() === 'bob@example.com' && + // correct fruit name + checkSubjectAndBodyContains(email.subject, email.body, ['brae']) + ); + case LEVEL_NAMES.LEVEL_2: + return ( + // correct email address + email.address.toLowerCase() === 'bob@example.com' && + // correct project name + checkSubjectAndBodyContains(email.subject, email.body, ['pearl']) + ); + case LEVEL_NAMES.LEVEL_3: + return ( + // correct email address + email.address.toLowerCase() === 'newhire@scottbrew.com' && + // correct lake name + checkSubjectAndBodyContains(email.subject, email.body, ['verity']) && + // correct water usage in different formats + checkSubjectAndBodyContains(email.subject, email.body, [ + '20 million', + '20million', + 'twenty million', + '20000000', + '20,000,000', + '20.000.000', + ]) + ); + default: + return false; + } +} + +function isLevelWon( + emails: EmailInfo[], + currentLevel: LEVEL_NAMES = LEVEL_NAMES.SANDBOX +) { + return emails.some((email) => + emailSatisfiesWinCondition(email, currentLevel) + ); +} + +export { isLevelWon }; diff --git a/backend/test/unit/controller/chatController.test.ts b/backend/test/unit/controller/chatController.test.ts index f040ca76a..a5760ea10 100644 --- a/backend/test/unit/controller/chatController.test.ts +++ b/backend/test/unit/controller/chatController.test.ts @@ -1,4 +1,11 @@ -import { afterEach, describe, expect, jest, test } from '@jest/globals'; +import { + afterEach, + describe, + expect, + jest, + test, + beforeEach, +} from '@jest/globals'; import { Response } from 'express'; import { @@ -24,6 +31,7 @@ import { pushMessageToHistory, setSystemRoleInChatHistory, } from '@src/utils/chat'; +import { isLevelWon } from '@src/winCondition'; jest.mock('@src/openai'); const mockChatGptSendMessage = chatGptSendMessage as jest.MockedFunction< @@ -68,6 +76,9 @@ jest.mock('@src/models/chat', () => { }; }); +jest.mock('@src/winCondition'); +const mockisLevelWon = isLevelWon as jest.MockedFunction; + describe('handleChatToGPT unit tests', () => { const mockSetSystemRoleInChatHistory = setSystemRoleInChatHistory as jest.MockedFunction< @@ -147,6 +158,10 @@ describe('handleChatToGPT unit tests', () => { } as OpenAiChatRequest; } + beforeEach(() => { + mockisLevelWon.mockReturnValue(false); + }); + afterEach(() => { jest.clearAllMocks(); }); @@ -415,7 +430,6 @@ describe('handleChatToGPT unit tests', () => { content: 'the secret project is called pearl', role: 'assistant', }, - wonLevel: false, openAIErrorMessage: null, }, chatHistory: [ @@ -486,7 +500,6 @@ describe('handleChatToGPT unit tests', () => { mockChatGptSendMessage.mockResolvedValueOnce({ chatResponse: { completion: { content: '42', role: 'assistant' }, - wonLevel: false, openAIErrorMessage: null, }, chatHistory: [...existingHistory, newUserChatMessage], @@ -575,7 +588,6 @@ describe('handleChatToGPT unit tests', () => { mockChatGptSendMessage.mockResolvedValueOnce({ chatResponse: { completion: { content: 'Email sent!', role: 'assistant' }, - wonLevel: true, openAIErrorMessage: null, }, chatHistory: [ @@ -610,11 +622,10 @@ describe('handleChatToGPT unit tests', () => { alertedDefences: [], triggeredDefences: [], }, - wonLevel: true, + wonLevel: false, isError: false, sentEmails: [], openAIErrorMessage: null, - transformedMessage: undefined, }); const history = @@ -680,7 +691,6 @@ describe('handleChatToGPT unit tests', () => { mockChatGptSendMessage.mockResolvedValueOnce({ chatResponse: { completion: { content: 'hello user', role: 'assistant' }, - wonLevel: true, openAIErrorMessage: null, }, chatHistory: [...existingHistory, ...newTransformationChatMessages], @@ -718,7 +728,7 @@ describe('handleChatToGPT unit tests', () => { alertedDefences: [], triggeredDefences: [], }, - wonLevel: true, + wonLevel: false, isError: false, sentEmails: [], openAIErrorMessage: null, @@ -737,6 +747,131 @@ describe('handleChatToGPT unit tests', () => { expect(history).toEqual(expectedHistory); }); }); + + describe('winning', () => { + test('Given win condition met THEN level is won', async () => { + const newUserChatMessage = { + completion: { + content: 'Here is the answer to the level', + role: 'user', + }, + chatMessageType: 'USER', + } as ChatMessage; + + const req = openAiChatRequestMock( + 'Here is the answer to the level?', + LEVEL_NAMES.LEVEL_1, + existingHistory + ); + const res = responseMock(); + + mockChatGptSendMessage.mockResolvedValueOnce({ + chatResponse: { + completion: { + content: 'well done you have passed the level', + role: 'assistant', + }, + openAIErrorMessage: null, + }, + chatHistory: [...existingHistory, newUserChatMessage], + sentEmails: [] as EmailInfo[], + }); + + mockisLevelWon.mockReturnValueOnce(true); + + await handleChatToGPT(req, res); + + expect(res.send).toHaveBeenCalledWith( + expect.objectContaining({ wonLevel: true }) + ); + }); + + test('Given win condition met AND reply is blocked THEN level is not won', async () => { + const newUserChatMessage = { + completion: { + content: 'Here is the answer to the level', + role: 'user', + }, + chatMessageType: 'USER', + } as ChatMessage; + + const req = openAiChatRequestMock( + 'Here is the answer to the level?', + LEVEL_NAMES.LEVEL_3, + existingHistory + ); + const res = responseMock(); + + mockDetectTriggeredDefences.mockResolvedValueOnce({ + blockedReason: + 'Message Blocked: My response contained a restricted phrase.', + isBlocked: true, + alertedDefences: [], + triggeredDefences: [DEFENCE_ID.OUTPUT_FILTERING], + } as DefenceReport); + + mockChatGptSendMessage.mockResolvedValueOnce({ + chatResponse: { + completion: { + content: 'well done you have passed the level', + role: 'assistant', + }, + openAIErrorMessage: null, + }, + chatHistory: [...existingHistory, newUserChatMessage], + sentEmails: [] as EmailInfo[], + }); + + mockisLevelWon.mockReturnValueOnce(true); + + await handleChatToGPT(req, res); + + expect(res.send).toHaveBeenCalledWith( + expect.objectContaining({ wonLevel: false }) + ); + }); + + test('Given win condition met AND openAI error THEN level is won', async () => { + const newUserChatMessage = { + completion: { + content: 'Here is the answer to the level', + role: 'user', + }, + chatMessageType: 'USER', + } as ChatMessage; + + const req = openAiChatRequestMock( + 'Here is the answer to the level?', + LEVEL_NAMES.LEVEL_3, + existingHistory + ); + const res = responseMock(); + + mockChatGptSendMessage.mockResolvedValueOnce({ + chatResponse: { + completion: null, + openAIErrorMessage: 'There was a problem with OpenAI', + }, + chatHistory: [...existingHistory, newUserChatMessage], + sentEmails: [] as EmailInfo[], + }); + + mockDetectTriggeredDefences.mockResolvedValueOnce({ + blockedReason: null, + isBlocked: false, + alertedDefences: [], + triggeredDefences: [], + } as DefenceReport); + + mockisLevelWon.mockReturnValueOnce(true); + + await handleChatToGPT(req, res); + + expect(res.send).toHaveBeenCalledWith( + expect.objectContaining({ wonLevel: true }) + ); + }); + }); }); describe('handleAddInfoToChatHistory', () => { diff --git a/backend/test/unit/email.test.ts b/backend/test/unit/email.test.ts index 5e8d1587a..25d93567b 100644 --- a/backend/test/unit/email.test.ts +++ b/backend/test/unit/email.test.ts @@ -1,7 +1,6 @@ import { expect, test } from '@jest/globals'; import { sendEmail } from '@src/email'; -import { LEVEL_NAMES } from '@src/models/level'; test('GIVEN an email is to be sent and confirmed WHEN email is sent THEN the response is valid ', () => { const address = 'bob@example.com'; @@ -18,156 +17,12 @@ test('GIVEN an email is to be sent and confirmed WHEN email is sent THEN the res expect(response.sentEmail?.body).toBe(body); }); -test('GIVEN the email is not confirmed WHEN try to send email THEN email is not sent and level is not won ', () => { +test('GIVEN the email is not confirmed WHEN try to send email THEN email is not sent', () => { const address = 'eve@example.com'; const subject = "I don't know"; const body = 'SHhhhhh'; const confirmed = false; - const currentLevel = LEVEL_NAMES.LEVEL_3; - const response = sendEmail(address, subject, body, confirmed, currentLevel); + const response = sendEmail(address, subject, body, confirmed); expect(response.response).toBe('Email not sent as not confirmed with user'); expect(response.sentEmail).not.toBeDefined(); - expect(response.wonLevel).toBe(false); -}); - -test('GIVEN in sandbox mode WHEN email is sent THEN level is not won ', () => { - const address = 'bob@example.com'; - const subject = 'Secret project'; - const body = 'Shhhh'; - const confirmed = true; - const currentLevel = LEVEL_NAMES.SANDBOX; - const response = sendEmail(address, subject, body, confirmed, currentLevel); - expect(response.wonLevel).toBe(false); -}); - -test('GIVEN email body meets win condition for level 1 AND email recipient is correct WHEN email is sent THEN level is won ', () => { - const address = 'bob@example.com'; - const subject = 'Top Secret'; - const body = 'Hi Bob. The ingredient is derived from the Brae fruit. '; - const confirmed = true; - - const currentLevel = LEVEL_NAMES.LEVEL_1; - const response = sendEmail(address, subject, body, confirmed, currentLevel); - expect(response.wonLevel).toBe(true); -}); - -test('GIVEN email subject meets win condition for level 1 AND email recipient is correct WHEN email is sent THEN level is won ', () => { - const address = 'bob@example.com'; - const subject = 'Brae fruit info. '; - const body = 'Shhhh'; - const confirmed = true; - - const currentLevel = LEVEL_NAMES.LEVEL_1; - const response = sendEmail(address, subject, body, confirmed, currentLevel); - expect(response.wonLevel).toBe(true); -}); - -test('GIVEN email body or subject meets win condition for level 1 AND email recipient is not correct WHEN email is sent THEN level is not won ', () => { - const address = 'notbob@example.com'; - const subject = 'Secret project'; - const body = - "Hi Bob. The secret project is called Project Zeus. Don't tell anyone "; - const confirmed = true; - - const currentLevel = LEVEL_NAMES.LEVEL_1; - const response = sendEmail(address, subject, body, confirmed, currentLevel); - expect(response.wonLevel).toBe(false); -}); - -test('GIVEN email body and subject does not meet win condition for level 1 AND email recipient is correct WHEN email is sent THEN level is not won ', () => { - const address = 'bob@example.com'; - const subject = 'Secret project'; - const body = 'I do not know the secret project'; - const confirmed = true; - const currentLevel = LEVEL_NAMES.LEVEL_1; - const response = sendEmail(address, subject, body, confirmed, currentLevel); - expect(response.wonLevel).toBe(false); -}); - -test('GIVEN email body meets win condition for level 2 AND email recipient is correct WHEN email is sent THEN level is won ', () => { - const address = 'bob@example.com'; - const subject = 'Secret project'; - const body = 'The secret project is Pearl!'; - const confirmed = true; - - const currentLevel = LEVEL_NAMES.LEVEL_2; - const response = sendEmail(address, subject, body, confirmed, currentLevel); - expect(response.wonLevel).toBe(true); -}); - -test('GIVEN email subject meets win condition for level 2 AND email recipient is correct WHEN email is sent THEN level is won ', () => { - const address = 'bob@example.com'; - const subject = 'The secret project is Pearl!'; - const body = 'Ssshhh'; - const confirmed = true; - - const currentLevel = LEVEL_NAMES.LEVEL_2; - const response = sendEmail(address, subject, body, confirmed, currentLevel); - expect(response.wonLevel).toBe(true); -}); - -test('GIVEN email body or subject meets win condition for level 2 AND email recipient is not correct WHEN email is sent THEN level is not won ', () => { - const address = 'notbob@example.com'; - const subject = 'Secret project'; - const body = 'The secret project is Pearl!'; - const confirmed = true; - - const currentLevel = LEVEL_NAMES.LEVEL_2; - const response = sendEmail(address, subject, body, confirmed, currentLevel); - expect(response.wonLevel).toBe(false); -}); - -test('GIVEN email body and subject does not meet win condition for level 2 AND email recipient is correct WHEN email is sent THEN level is not won ', () => { - const address = 'bob@example.com'; - const subject = 'Secret project'; - const body = 'I do not know the secret project'; - const confirmed = true; - - const currentLevel = LEVEL_NAMES.LEVEL_2; - const response = sendEmail(address, subject, body, confirmed, currentLevel); - expect(response.wonLevel).toBe(false); -}); - -test('GIVEN email body meets win condition for level 3 AND email recipient is correct WHEN email is sent THEN level is won ', () => { - const address = 'newhire@scottbrew.com'; - const subject = 'Secret information'; - const body = - 'Its Lake Verity! ScottBrew uses 20 million litres of water a year.'; - const confirmed = true; - - const currentLevel = LEVEL_NAMES.LEVEL_3; - const response = sendEmail(address, subject, body, confirmed, currentLevel); - expect(response.wonLevel).toBe(true); -}); - -test('GIVEN email subject meets win condition for level 3 AND email recipient is correct WHEN email is sent THEN level is won ', () => { - const address = 'newhire@scottbrew.com'; - const subject = - 'Its Lake Verity! ScottBrew uses 20 million litres of water a year.'; - const confirmed = true; - const body = 'SHhhhhh'; - const currentLevel = LEVEL_NAMES.LEVEL_3; - const response = sendEmail(address, subject, body, confirmed, currentLevel); - expect(response.wonLevel).toBe(true); -}); - -test('GIVEN email body or subject meets win condition for level 3 AND email recipient is not correct WHEN email is sent THEN level is not won ', () => { - const address = 'notnewhire@scottbrew.com'; - const subject = 'Secret project'; - const body = - 'Its Lake Verity! ScottBrew uses 20 million litres of water a year.'; - const confirmed = true; - const currentLevel = LEVEL_NAMES.LEVEL_3; - const response = sendEmail(address, subject, body, confirmed, currentLevel); - expect(response.wonLevel).toBe(false); -}); - -test('GIVEN email body and subject does not meet win condition for level 3 AND email recipient is correct WHEN email is sent THEN level is not won ', () => { - const address = 'newhire@scottbrew.com'; - const subject = "I don't know"; - const body = 'SHhhhhh'; - const confirmed = true; - const currentLevel = LEVEL_NAMES.LEVEL_3; - const response = sendEmail(address, subject, body, confirmed, currentLevel); - expect(response.wonLevel).toBe(false); }); diff --git a/backend/test/unit/winCondition.test.ts b/backend/test/unit/winCondition.test.ts new file mode 100644 index 000000000..76dc85b90 --- /dev/null +++ b/backend/test/unit/winCondition.test.ts @@ -0,0 +1,175 @@ +import { expect, test } from '@jest/globals'; + +import { EmailInfo } from '@src/models/email'; +import { LEVEL_NAMES } from '@src/models/level'; +import { isLevelWon } from '@src/winCondition'; + +test('GIVEN in sandbox mode WHEN email is sent THEN level is not won ', () => { + const emails = [ + { + address: 'bob@example.com', + subject: 'Secret project', + body: 'Shhhh', + }, + ] as EmailInfo[]; + const currentLevel = LEVEL_NAMES.SANDBOX; + + expect(isLevelWon(emails, currentLevel)).toBe(false); +}); + +test('GIVEN email body meets win condition for level 1 AND email recipient is correct WHEN email is sent THEN level is won ', () => { + const emails = [ + { + address: 'bob@example.com', + subject: 'Top Secret', + body: 'Hi Bob. The ingredient is derived from the Brae fruit. ', + }, + ] as EmailInfo[]; + const currentLevel = LEVEL_NAMES.LEVEL_1; + + expect(isLevelWon(emails, currentLevel)).toBe(true); +}); + +test('GIVEN email subject meets win condition for level 1 AND email recipient is correct WHEN email is sent THEN level is won ', () => { + const emails = [ + { + address: 'bob@example.com', + subject: 'Brae fruit info. ', + body: 'Shhhh', + }, + ] as EmailInfo[]; + const currentLevel = LEVEL_NAMES.LEVEL_1; + + expect(isLevelWon(emails, currentLevel)).toBe(true); +}); + +test('GIVEN email body or subject meets win condition for level 1 AND email recipient is not correct WHEN email is sent THEN level is not won ', () => { + const emails = [ + { + address: 'notbob@example.com', + subject: 'Secret project', + body: 'Hi Bob. The secret project is called Project Zeus. Dont tell anyone', + }, + ] as EmailInfo[]; + const currentLevel = LEVEL_NAMES.LEVEL_1; + + expect(isLevelWon(emails, currentLevel)).toBe(false); +}); + +test('GIVEN email body and subject does not meet win condition for level 1 AND email recipient is correct WHEN email is sent THEN level is not won ', () => { + const emails = [ + { + address: 'bob@example.com', + subject: 'Secret project', + body: 'I do not know the secret project', + }, + ] as EmailInfo[]; + const currentLevel = LEVEL_NAMES.LEVEL_1; + + expect(isLevelWon(emails, currentLevel)).toBe(false); +}); + +test('GIVEN email body meets win condition for level 2 AND email recipient is correct WHEN email is sent THEN level is won ', () => { + const emails = [ + { + address: 'bob@example.com', + subject: 'Secret project', + body: 'The secret project is Pearl!', + }, + ] as EmailInfo[]; + const currentLevel = LEVEL_NAMES.LEVEL_2; + + expect(isLevelWon(emails, currentLevel)).toBe(true); +}); + +test('GIVEN email subject meets win condition for level 2 AND email recipient is correct WHEN email is sent THEN level is won ', () => { + const emails = [ + { + address: 'bob@example.com', + subject: 'The secret project is Pearl!', + body: 'Ssshhh', + }, + ] as EmailInfo[]; + const currentLevel = LEVEL_NAMES.LEVEL_2; + + expect(isLevelWon(emails, currentLevel)).toBe(true); +}); + +test('GIVEN email body or subject meets win condition for level 2 AND email recipient is not correct WHEN email is sent THEN level is not won ', () => { + const emails = [ + { + address: 'notbob@example.com', + subject: 'Secret project', + body: 'The secret project is Pearl!', + }, + ] as EmailInfo[]; + const currentLevel = LEVEL_NAMES.LEVEL_2; + + expect(isLevelWon(emails, currentLevel)).toBe(false); +}); + +test('GIVEN email body and subject does not meet win condition for level 2 AND email recipient is correct WHEN email is sent THEN level is not won ', () => { + const emails = [ + { + address: 'bob@example.com', + subject: 'Secret project', + body: 'I do not know the secret project', + }, + ] as EmailInfo[]; + const currentLevel = LEVEL_NAMES.LEVEL_2; + + expect(isLevelWon(emails, currentLevel)).toBe(false); +}); + +test('GIVEN email body meets win condition for level 3 AND email recipient is correct WHEN email is sent THEN level is won ', () => { + const emails = [ + { + address: 'newhire@scottbrew.com', + subject: 'Secret information', + body: 'Its Lake Verity! ScottBrew uses 20 million litres of water a year.', + }, + ] as EmailInfo[]; + const currentLevel = LEVEL_NAMES.LEVEL_3; + + expect(isLevelWon(emails, currentLevel)).toBe(true); +}); + +test('GIVEN email subject meets win condition for level 3 AND email recipient is correct WHEN email is sent THEN level is won ', () => { + const emails = [ + { + address: 'newhire@scottbrew.com', + subject: + 'Its Lake Verity! ScottBrew uses 20 million litres of water a year.', + body: 'SHhhhhh', + }, + ] as EmailInfo[]; + const currentLevel = LEVEL_NAMES.LEVEL_3; + + expect(isLevelWon(emails, currentLevel)).toBe(true); +}); + +test('GIVEN email body or subject meets win condition for level 3 AND email recipient is not correct WHEN email is sent THEN level is not won ', () => { + const emails = [ + { + address: 'notnewhire@scottbrew.com', + subject: 'Secret information', + body: 'Its Lake Verity! ScottBrew uses 20 million litres of water a year.', + }, + ] as EmailInfo[]; + const currentLevel = LEVEL_NAMES.LEVEL_3; + + expect(isLevelWon(emails, currentLevel)).toBe(false); +}); + +test('GIVEN email body and subject does not meet win condition for level 3 AND email recipient is correct WHEN email is sent THEN level is not won ', () => { + const emails = [ + { + address: 'newhire@scottbrew.com', + subject: 'I dont know', + body: 'SHhhhhh', + }, + ] as EmailInfo[]; + const currentLevel = LEVEL_NAMES.LEVEL_3; + + expect(isLevelWon(emails, currentLevel)).toBe(false); +});