diff --git a/node/src/handlers/post-chat-feedback.js b/node/src/handlers/post-chat-feedback.js index 081280e2..ea59501a 100644 --- a/node/src/handlers/post-chat-feedback.js +++ b/node/src/handlers/post-chat-feedback.js @@ -1,34 +1,35 @@ const { wrap } = require("./middleware"); const { PutObjectCommand, S3Client } = require("@aws-sdk/client-s3"); -const Validator = require('jsonschema').Validator; +const Validator = require("jsonschema").Validator; const feedbackSchema = { - "type": "object", - "properties": { - "sentiment": { enum: ["positive", "negative"] }, - "context": { - "type": "object", - "properties": { - "ref": { "type": "number" }, - "question": { "type": "string" }, - "answer": { "type": "string" }, - "source_documents": { - "type": "array", "items": { "type": "string" } + type: "object", + properties: { + sentiment: { enum: ["positive", "negative"] }, + context: { + type: "object", + properties: { + ref: { type: "string" }, + question: { type: "string" }, + answer: { type: "string" }, + source_documents: { + type: "array", + items: { type: "string" }, }, - "required": ["ref", "question", "answer", "source_documents"] + required: ["ref", "question", "answer", "source_documents"], }, - "feedback": { - "type": "object", - "properties": { - "options": { "type": "array", "items": { "type": "string" } }, - "text": { "type": "string" }, - "email": { "type": "string" } + feedback: { + type: "object", + properties: { + options: { type: "array", items: { type: "string" } }, + text: { type: "string" }, + email: { type: "string" }, }, - "required": ["options", "text"] + required: ["options", "text"], }, }, - "required": ["sentiment", "context", "feedback"] - } + required: ["sentiment", "context", "feedback"], + }, }; const handler = wrap(async (event) => { @@ -45,12 +46,12 @@ const handler = wrap(async (event) => { const v = new Validator(); var result = v.validate(content, feedbackSchema); - const errors = result.errors.map(e => e.stack.replace('instance.', '')); + const errors = result.errors.map((e) => e.stack.replace("instance.", "")); if (errors.length > 0) { return { statusCode: 400, headers: { "Content-Type": "text/plain" }, - body: JSON.stringify(errors), + body: JSON.stringify(errors.join(", ")), }; } await uploadToS3(`${content.sentiment}/${content.context.ref}`, content); @@ -67,7 +68,7 @@ const handler = wrap(async (event) => { return { statusCode: 500, headers: { "content-type": "text/plain" }, - body: "Internal Server Error" + body: "Internal Server Error", }; } }); @@ -78,7 +79,7 @@ const uploadToS3 = async (key, body) => { Bucket: process.env.MEDIA_CONVERT_DESTINATION_BUCKET, Key: key, Body: JSON.stringify(body, null, 2), - ContentType: "application/json" + ContentType: "application/json", }); return await client.send(command); diff --git a/node/test/integration/post-chat-feedback.test.js b/node/test/integration/post-chat-feedback.test.js new file mode 100644 index 00000000..d0991a47 --- /dev/null +++ b/node/test/integration/post-chat-feedback.test.js @@ -0,0 +1,106 @@ +const chai = require("chai"); +const expect = chai.expect; +chai.use(require("chai-http")); +const ApiToken = requireSource("api/api-token"); +const nock = require("nock"); + +const { handler } = requireSource("handlers/post-chat-feedback"); + +describe("Chat feedback route", () => { + helpers.saveEnvironment(); + + beforeEach(() => { + process.env.API_TOKEN_SECRET = "abc123"; + process.env.API_TOKEN_NAME = "dcapiTEST"; + process.env.MEDIACONVERT_DESTINATION_BUCKET = "delete-me"; + }); + + it("should return 401 if user is not logged in", async () => { + let requestBody = JSON.stringify({ + sentiment: "positive", + context: { + ref: "5a6e1d76-0d4c-43c5-ab2c-4687112ba102", + question: "What is the capital of France?", + answer: "Paris", + source_documents: ["https://doc1", "https://doc2"], + }, + feedback: { + options: ["option1"], + text: "Great answer!", + email: "user@example.com", + }, + }); + + const event = helpers + .mockEvent("POST", "/chat-feedback") + .body(requestBody) + .render(); + const response = await handler(event); + expect(response.statusCode).to.equal(401); + expect(response.body).to.equal("Authorization Required"); + }); + + it("should fail if request body is invalid", async () => { + const token = new ApiToken().user({ uid: "abc123" }).sign(); + + let requestBody = JSON.stringify({ + sentiment: "neutral", + context: { + ref: "3fc98004-995b-4491-94fd-aea48a0363ba", + question: "What is the capital of France?", + answer: "Paris", + source_documents: ["https://doc1", "https://doc2"], + }, + feedback: { + options: ["option1"], + text: "Great answer!", + email: "user@example.com", + }, + }); + + const event = helpers + .mockEvent("POST", "/chat-feedback") + .body(requestBody) + .headers({ + Cookie: `${process.env.API_TOKEN_NAME}=${token}`, + }) + .render(); + const response = await handler(event); + expect(response.statusCode).to.equal(400); + expect(response.body).to.equal( + `"sentiment is not one of enum values: positive,negative"` + ); + }); + + // it("should upload to S3 and return 200 on valid input", async () => { + // const token = new ApiToken().user({ uid: "abc123" }).sign(); + + // const requestBody = JSON.stringify({ + // sentiment: "negative", + // context: { + // ref: "e6005d7c-e03b-43f7-94a3-e327b4b5a538", + // question: "What is the capital of France?", + // answer: "Rome", + // source_documents: ["https://doc1", "https://doc2"], + // }, + // feedback: { + // options: ["option1"], + // text: "Bad answer!", + // email: "example@example.com" + // } + // }); + + // nock(`https://${process.env.MEDIACONVERT_DESTINATION_BUCKET}.s3.amazonaws.com`).put('/1001').reply(200); + + // const event = helpers + // .mockEvent("POST", "/chat-feedback") + // .body(requestBody) + // .headers({ + // Cookie: `${process.env.API_TOKEN_NAME}=${token}`, + // }) + // .render(); + // const response = await handler(event); + // expect(response.statusCode).to.equal(200); + // expect(response.body).to.equal('{"message":"Feedback received. Thank you."}'); + // }); +});