test: move server tests into `test/server.test.ts`

This commit is contained in:
Gregor Martynus 2020-11-22 15:52:00 -08:00
parent 3d90806c95
commit e0d34c7f80
3 changed files with 194 additions and 193 deletions

View File

@ -74,10 +74,17 @@ describe("Probot", () => {
new Probot({ Octokit: MyOctokit });
});
it("sets version", async () => {
const probot = new Probot({});
expect(probot.version).toBe("0.0.0-development");
});
});
describe("webhook delivery", () => {
describe("webhooks", () => {
it("responds with the correct error if webhook secret does not match", async () => {
expect.assertions(1);
probot.log.error = jest.fn();
probot.webhooks.on("push", () => {
throw new Error("X-Hub-Signature does not match blob signature");
@ -93,6 +100,8 @@ describe("Probot", () => {
});
it("responds with the correct error if webhook secret is not found", async () => {
expect.assertions(1);
probot.log.error = jest.fn();
probot.webhooks.on("push", () => {
throw new Error("No X-Hub-Signature found on request");
@ -108,6 +117,8 @@ describe("Probot", () => {
});
it("responds with the correct error if webhook secret is wrong", async () => {
expect.assertions(1);
probot.log.error = jest.fn();
probot.webhooks.on("push", () => {
throw new Error(
@ -125,6 +136,8 @@ describe("Probot", () => {
});
it("responds with the correct error if the PEM file is missing", async () => {
expect.assertions(1);
probot.log.error = jest.fn();
probot.webhooks.onAny(() => {
throw new Error(
@ -142,6 +155,8 @@ describe("Probot", () => {
});
it("responds with the correct error if the jwt could not be decoded", async () => {
expect.assertions(1);
probot.log.error = jest.fn();
probot.webhooks.onAny(() => {
throw new Error(
@ -159,111 +174,6 @@ describe("Probot", () => {
});
});
describe("server", () => {
it("prefixes paths with route name", () => {
probot.load(({ app, getRouter }) => {
const router = getRouter("/my-app");
router.get("/foo", (req, res) => res.end("foo"));
});
return request(probot.server).get("/my-app/foo").expect(200, "foo");
});
it("allows routes with no path", () => {
probot.load(({ app, getRouter }) => {
const router = getRouter();
router.get("/foo", (req, res) => res.end("foo"));
});
return request(probot.server).get("/foo").expect(200, "foo");
});
it("allows you to overwrite the root path", () => {
probot.load(({ app, getRouter }) => {
const router = getRouter();
router.get("/", (req, res) => res.end("foo"));
});
return request(probot.server).get("/").expect(200, "foo");
});
it("isolates apps from affecting each other", async () => {
["foo", "bar"].forEach((name) => {
probot.load(({ app, getRouter }) => {
const router = getRouter("/" + name);
router.use((req, res, next) => {
res.append("X-Test", name);
next();
});
router.get("/hello", (req, res) => res.end(name));
});
});
await request(probot.server)
.get("/foo/hello")
.expect(200, "foo")
.expect("X-Test", "foo");
await request(probot.server)
.get("/bar/hello")
.expect(200, "bar")
.expect("X-Test", "bar");
});
it("allows users to configure webhook paths", async () => {
probot = new Probot({
webhookPath: "/webhook",
githubToken: "faketoken",
});
// Error handler to avoid printing logs
// tslint:disable-next-line handle-callback-error
probot.server.use(
(error: any, req: Request, res: Response, next: NextFunction) => {}
);
probot.load(({ getRouter }) => {
const router = getRouter();
router.get("/webhook", (req, res) => res.end("get-webhook"));
router.post("/webhook", (req, res) => res.end("post-webhook"));
});
// GET requests should succeed
await request(probot.server).get("/webhook").expect(200, "get-webhook");
// POST requests should fail b/c webhook path has precedence
await request(probot.server).post("/webhook").expect(400);
});
it("defaults webhook path to `/`", async () => {
// Error handler to avoid printing logs
// tslint:disable-next-line handle-callback-error
probot.server.use(
(error: any, req: Request, res: Response, next: NextFunction) => {}
);
// POST requests to `/` should 400 b/c webhook signature will fail
await request(probot.server).post("/").expect(400);
});
it("responds with 500 on error", async () => {
probot.server.get("/boom", () => {
throw new Error("boom");
});
await request(probot.server).get("/boom").expect(500);
});
it("responds with 500 on async error", async () => {
probot.server.get("/boom", () => {
return Promise.reject(new Error("boom"));
});
await request(probot.server).get("/boom").expect(500);
});
});
describe("receive", () => {
it("forwards events to each app", async () => {
const spy = jest.fn();
@ -400,54 +310,6 @@ describe("Probot", () => {
});
});
describe("start", () => {
beforeEach(() => {
process.exit = jest.fn() as any; // we dont want to terminate the test
});
it("should expect the correct error if port already in use", (next) => {
expect.assertions(2);
// block port 3001
const http = require("http");
const blockade = http.createServer().listen(3001, () => {
const testApp = new Probot({ port: 3001 });
testApp.log.error = jest.fn();
const server = testApp.start().addListener("error", () => {
expect(testApp.log.error).toHaveBeenCalledWith(
"Port 3001 is already in use. You can define the PORT environment variable to use a different port."
);
expect(process.exit).toHaveBeenCalledWith(1);
server.close(() => blockade.close(() => next()));
});
});
});
it("should listen to port when not in use", (next) => {
expect.assertions(1);
const testApp = new Probot({ port: 3001, webhookProxy: undefined });
testApp.log.info = jest.fn();
const server = testApp.start().on("listening", () => {
expect(testApp.log.info).toHaveBeenCalledWith(
"Listening on http://localhost:3001"
);
server.close(() => next());
});
});
it("respects host/ip config when starting up HTTP server", (next) => {
const testApp = new Probot({ port: 3002, host: "127.0.0.1" });
const spy = jest.spyOn(testApp.server, "listen");
const server = testApp.start().on("listening", () => {
expect(spy.mock.calls[0][0]).toBe(3002);
expect(spy.mock.calls[0][1]).toBe("127.0.0.1");
spy.mockRestore();
server.close(() => next());
});
});
});
describe("load", () => {
it("app sends request with JWT authentication", async () => {
expect.assertions(3);

View File

@ -1,14 +1,16 @@
import Stream from "stream";
import path = require("path");
import request from "supertest";
import { sign } from "@octokit/webhooks";
import { Probot, run, Server } from "../src";
import path = require("path");
// tslint:disable:no-empty
describe("run", () => {
let probot: Probot;
let server: Server;
let output: any;
let env: NodeJS.ProcessEnv;
const streamLogsToOutput = new Stream.Writable({ objectMode: true });
streamLogsToOutput._write = (object, encoding, done) => {
@ -19,34 +21,26 @@ describe("run", () => {
beforeEach(() => {
// Clear log output
output = [];
process.env.DISABLE_WEBHOOK_EVENT_CHECK = "true";
probot = new Probot({ githubToken: "faketoken" });
env = {
DISABLE_WEBHOOK_EVENT_CHECK: "true",
APP_ID: "1",
PRIVATE_KEY_PATH: path.join(__dirname, "test-private-key.pem"),
WEBHOOK_PROXY_URL: "https://smee.io/EfHXC9BFfGAxbM6J",
WEBHOOK_SECRET: "secret",
LOG_LEVEL: "fatal",
};
});
describe("run", () => {
let env: NodeJS.ProcessEnv;
beforeAll(() => {
env = { ...process.env };
process.env.APP_ID = "1";
process.env.PRIVATE_KEY_PATH = path.join(
__dirname,
"test-private-key.pem"
);
process.env.WEBHOOK_PROXY_URL = "https://smee.io/EfHXC9BFfGAxbM6J";
process.env.WEBHOOK_SECRET = "secret";
});
afterAll(() => {
process.env = env;
});
describe("params", () => {
it("runs with a function as argument", async () => {
let initialized = false;
server = await run(() => {
initialized = true;
});
server = await run(
() => {
initialized = true;
},
{ env }
);
expect(initialized).toBeTruthy();
await server.stop();
});
@ -58,28 +52,61 @@ describe("run", () => {
it("runs without config and loads the setup app", async () => {
let initialized = false;
delete process.env.PRIVATE_KEY_PATH;
process.env.PORT = "3003";
delete env.PRIVATE_KEY_PATH;
env.PORT = "3003";
return new Promise(async (resolve) => {
server = await run(({ app }: { app: Probot }) => {
initialized = true;
});
server = await run(
({ app }: { app: Probot }) => {
initialized = true;
},
{ env }
);
expect(initialized).toBeFalsy();
await server.stop();
resolve(null);
});
});
});
it("has version", async () => {
return new Promise(async (resolve) => {
server = await run(({ app }: { app: Probot }) => {});
expect(probot.version).toBe("0.0.0-development");
await server.stop();
describe("webhooks", () => {
it("POST /", async () => {
server = await run(() => {}, { env });
resolve(null);
const dataString = require("./fixtures/webhook/push.json");
await request(server.app)
.post("/")
.send(dataString)
.set("x-github-event", "push")
.set("x-hub-signature", sign("secret", dataString))
.set("x-github-delivery", "123")
.expect(200);
await server.stop();
});
it("custom webhook path", async () => {
server = await run(() => {}, {
env: {
...env,
WEBHOOK_SECRET: "secret",
WEBHOOK_PATH: "/custom-webhook",
},
});
const dataString = require("./fixtures/webhook/push.json");
await request(server.app)
.post("/custom-webhook")
.send(dataString)
.set("x-github-event", "push")
.set("x-hub-signature", sign("secret", dataString))
.set("x-github-delivery", "123")
.expect(200);
await server.stop();
});
});
});

View File

@ -41,7 +41,7 @@ describe("Server", () => {
});
});
describe("webhook handler", () => {
describe("webhook handler (POST /)", () => {
it("should 500 on a webhook error", async () => {
webhook.mockImplementation(
(req: Request, res: Response, callback: NextFunction) =>
@ -53,11 +53,123 @@ describe("Server", () => {
});
});
describe("with an unknown url", () => {
describe("GET unknown URL", () => {
it("responds with 404", async () => {
await request(server.app).get("/notfound").expect(404);
expect(output.length).toEqual(1);
expect(output[0].msg).toContain("GET /notfound 404 -");
});
});
describe(".start() / .stop()", () => {
it("should expect the correct error if port already in use", (next) => {
expect.assertions(1);
// block port 3001
const http = require("http");
const blockade = http.createServer().listen(3001, async () => {
const server = new Server({
log: pino(streamLogsToOutput),
port: 3001,
});
try {
await server.start();
} catch (error) {
expect(error.message).toEqual(
"Port 3001 is already in use. You can define the PORT environment variable to use a different port."
);
}
await server.stop();
blockade.close(() => next());
});
});
it("should listen to port when not in use", async () => {
const testApp = new Server({ port: 3001, log: pino(streamLogsToOutput) });
await testApp.start();
expect(output.length).toEqual(2);
expect(output[1].msg).toEqual("Listening on http://localhost:3001");
await testApp.stop();
});
it("respects host/ip config when starting up HTTP server", async () => {
const testApp = new Server({
port: 3002,
host: "127.0.0.1",
log: pino(streamLogsToOutput),
});
await testApp.start();
expect(output.length).toEqual(2);
expect(output[1].msg).toEqual("Listening on http://127.0.0.1:3002");
await testApp.stop();
});
});
describe("router", () => {
it("prefixes paths with route name", () => {
const router = server.router("/my-app");
router.get("/foo", (req, res) => res.end("foo"));
return request(server.app).get("/my-app/foo").expect(200, "foo");
});
it("allows routes with no path", () => {
const router = server.router();
router.get("/foo", (req, res) => res.end("foo"));
return request(server.app).get("/foo").expect(200, "foo");
});
it("allows you to overwrite the root path", () => {
const router = server.router();
router.get("/", (req, res) => res.end("foo"));
return request(server.app).get("/").expect(200, "foo");
});
it("isolates apps from affecting each other", async () => {
["foo", "bar"].forEach((name) => {
const router = server.router("/" + name);
router.use((req, res, next) => {
res.append("X-Test", name);
next();
});
router.get("/hello", (req, res) => res.end(name));
});
await request(server.app)
.get("/foo/hello")
.expect(200, "foo")
.expect("X-Test", "foo");
await request(server.app)
.get("/bar/hello")
.expect(200, "bar")
.expect("X-Test", "bar");
});
it("responds with 500 on error", async () => {
server.app.get("/boom", () => {
throw new Error("boom");
});
await request(server.app).get("/boom").expect(500);
});
it("responds with 500 on async error", async () => {
server.app.get("/boom", () => {
return Promise.reject(new Error("boom"));
});
await request(server.app).get("/boom").expect(500);
});
});
});