add: unit and integration testing
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
const request = require("supertest");
|
||||
const app = require("../src/app");
|
||||
|
||||
describe("GET /search", () => {
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test("returns 400 when query is missing", async () => {
|
||||
const response = await request(app).get("/search");
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.text).toBe("Missing search query");
|
||||
});
|
||||
|
||||
test("returns filtered search results", async () => {
|
||||
global.fetch = jest.fn().mockResolvedValue({
|
||||
json: jest.fn().mockResolvedValue({
|
||||
results: [
|
||||
{
|
||||
url: "https://example.com",
|
||||
title: "Example",
|
||||
content: "Content",
|
||||
irrelevant: "removed"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
const response = await request(app)
|
||||
.get("/search")
|
||||
.query({ query: "nodejs" });
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers["content-type"]).toMatch(/application\/json/);
|
||||
expect(response.headers["content-disposition"]).toContain(
|
||||
'attachment; filename="output.json"'
|
||||
);
|
||||
|
||||
expect(response.body).toEqual([
|
||||
{
|
||||
url: "https://example.com",
|
||||
title: "Example",
|
||||
content: "Content"
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
const { filterJson } = require("../src/search");
|
||||
|
||||
describe("filterJson", () => {
|
||||
// deliberately adding an extra property that is not supposed to be there
|
||||
test("keeps only url, title, and content", () => {
|
||||
const input = [
|
||||
{
|
||||
url: "https://example.com",
|
||||
title: "Example",
|
||||
content: "Some content",
|
||||
engine: "startpage",
|
||||
score: 0.9
|
||||
}
|
||||
];
|
||||
|
||||
expect(filterJson(input)).toEqual([
|
||||
{
|
||||
url: "https://example.com",
|
||||
title: "Example",
|
||||
content: "Some content"
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
test("returns an empty array for empty input", () => {
|
||||
expect(filterJson([])).toEqual([]);
|
||||
});
|
||||
|
||||
test("handles multiple results", () => {
|
||||
const input = [
|
||||
{ url: "one", title: "One", content: "First" },
|
||||
{ url: "two", title: "Two", content: "Second" }
|
||||
];
|
||||
|
||||
expect(filterJson(input)).toEqual(input);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,131 @@
|
||||
const { handleSearch } = require("../src/search");
|
||||
|
||||
describe("handleSearch", () => {
|
||||
let req;
|
||||
let res;
|
||||
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
query: {}
|
||||
};
|
||||
|
||||
// using .mockReturnThis() because of chainable methods
|
||||
res = {
|
||||
status: jest.fn().mockReturnThis(),
|
||||
send: jest.fn().mockReturnThis(),
|
||||
setHeader: jest.fn()
|
||||
};
|
||||
});
|
||||
|
||||
// clean up the observed data after each test
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test("returns 400 when query is missing", async () => {
|
||||
global.fetch = jest.fn().mockResolvedValue({
|
||||
json: jest.fn().mockResolvedValue({
|
||||
results: [
|
||||
{
|
||||
url: "https://example.com",
|
||||
title: "Example",
|
||||
content: "Content",
|
||||
irrelevant: "removed"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
await handleSearch(req, res);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(400);
|
||||
expect(res.send).toHaveBeenCalledWith("Missing search query");
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("fetches search results and sends filtered JSON", async () => {
|
||||
req.query.query = "nodejs testing";
|
||||
|
||||
global.fetch = jest.fn().mockResolvedValue({
|
||||
json: jest.fn().mockResolvedValue({
|
||||
results: [
|
||||
{
|
||||
url: "https://example.com",
|
||||
title: "Example",
|
||||
content: "Content",
|
||||
score: 0.8
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
await handleSearch(req, res);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledTimes(1);
|
||||
|
||||
const [url, options] = global.fetch.mock.calls[0];
|
||||
|
||||
expect(url).toContain("/search?");
|
||||
expect(url).toContain("q=nodejs+testing");
|
||||
expect(url).toContain("engines=startpage");
|
||||
expect(url).toContain("format=json");
|
||||
expect(url).not.toContain("score=0.8");
|
||||
|
||||
expect(options).toEqual({
|
||||
headers: {
|
||||
Accept: "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.setHeader).toHaveBeenCalledWith(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
);
|
||||
|
||||
expect(res.setHeader).toHaveBeenCalledWith(
|
||||
"Content-Disposition",
|
||||
'attachment; filename="output.json"'
|
||||
);
|
||||
|
||||
expect(res.send).toHaveBeenCalledWith(
|
||||
JSON.stringify(
|
||||
[
|
||||
{
|
||||
url: "https://example.com",
|
||||
title: "Example",
|
||||
content: "Content"
|
||||
}
|
||||
],
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test("uses an empty array when the API omits results", async () => {
|
||||
req.query.query = "empty";
|
||||
|
||||
global.fetch = jest.fn().mockResolvedValue({
|
||||
json: jest.fn().mockResolvedValue({})
|
||||
});
|
||||
|
||||
await handleSearch(req, res);
|
||||
|
||||
expect(res.send).toHaveBeenCalledWith("[]");
|
||||
});
|
||||
|
||||
test("returns 500 when fetch fails", async () => {
|
||||
req.query.query = "error";
|
||||
|
||||
global.fetch = jest
|
||||
.fn()
|
||||
.mockRejectedValue(new Error("SearxNG unavailable"));
|
||||
|
||||
await handleSearch(req, res);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(500);
|
||||
expect(res.send).toHaveBeenCalledWith(
|
||||
"<p><b>Search failed!<br>See the console for the error.</b></p>"
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user