In my testing environment I'm trying to intercept my request to ChatGPT (shown below) and replace it with a mock response. The fetch
api returns a Response.body
as a ReadableStream
of content. In my implementation, I use the ReadableStream.getReader()
which creates a reader and locks the stream to it. While "some-condition" is true, we await the reader.read()
method which returns a series of objects (see the console.log() screenshot below). Their values are decoded and then passed into parser which handles the parsed content which eventually shows up in the UI.
Since directly interacting with the ChatGPT api results in flakey e2e tests, I'm trying to figure out is how to correctly stub/mock the response from /api/chat-stream
in a way that is consumable and shaped correctly as to render a fake response in my integration tests. I've shared the snippet of code that ultimately will be used to intercept the request, but I'm stumped on how to correctly implement it.
// this should intercept the request and provide the mock/stub
await page.route(/.*\/api\/chat-stream/, async (route) => {
await route.fulfill({
status: 200,
headers: {
"some-headers"
},
body: someBody,
});
});
request to chatgpt
// localhost:3000/api/chat-stream
export async function POST(request: Request) {
const { messages } = await request.json();
const completion = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
body: JSON.stringify({
model: "gpt-3.5-turbo",
messages: messages,
stream: true,
}),
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.NEXT_PUBLIC_OPENAI_API_KEY}`,
},
});
return new Response(completion.body, {
// completion.body is a readable stream as detailed above
// ReadableStream { locked: false, state: 'readable', supportsBYOB: false }
status: 200,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
});
}
api call that triggers and handles above request
export const handleStreamMessage = async (messages: any) => {
const response = await fetch("/api/chat-stream", {
method: "POST",
body: JSON.stringify({
messages: messages,
}),
headers: {
"Content-Type": "application/json",
},
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
const onParse: EventSourceParseCallback = (event) => {
if (event.type === "event") {
try {
const data: { choices: { delta: { content: string } }[] } = JSON.parse(
event.data
);
// filter for chatgpt "deltas" with content
data.choices
.filter(({ delta }) => !!delta.content)
.forEach(({ delta }) => {
// do something in react
setCurrentMessage((prev) => {
return `${prev || ""}${delta.content}`;
});
});
} catch (error) {
console.log("error", error);
}
}
};
const parser = createParser(onParse);
if (reader) {
while (some-condition) {
const readOperation = await reader.read();
console.log("readOperation", readOperation);
const dataString = decoder.decode(readOperation.value);
if (readOperation.done || dataString.includes("[DONE]")) {
break;
}
parser.feed(dataString);
}
}
};