Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions src/controllers/getPackagesPackageName.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

module.exports = {
version: 2,
docs: {
summary: "Show package details.",
responses: {
Expand Down Expand Up @@ -42,49 +43,57 @@ module.exports = {
* @param {object} context - The Endpoint Context.
* @returns {sso}
*/
async logic(params, context) {
// Lets first check if this is a bundled package we should return
const isBundled = context.bundled.isNameBundled(params.packageName);
async logic(ctx) {
const { params } = ctx;

// Lets first check if this is a bundled package we should return
ctx.timecop.start("bundle");
const isBundled = ctx.bundled.isNameBundled(params.packageName);
ctx.timecop.end("bundle");
if (isBundled.ok && isBundled.content) {
// This is in fact a bundled package
const bundledData = context.bundled.getBundledPackage(params.packageName);
const bundledData = ctx.bundled.getBundledPackage(params.packageName);

if (!bundledData.ok) {
const sso = new context.sso();
const sso = new ctx.sso();

return sso
.notOk()
.addContent(bundledData)
.addCalls("bundled.isBundled", isBundled);
}

const sso = new context.sso();
const sso = new ctx.sso();

return sso.isOk().addContent(bundledData.content);
}

let pack = await context.database.getPackageByName(
ctx.timecop.start("db");

let pack = await ctx.database.getPackageByName(
params.packageName,
true
);

ctx.timecop.end("db");
if (!pack.ok) {
const sso = new context.sso();
const sso = new ctx.sso();

return sso.notOk().addContent(pack).addCalls("db.getPackageByName", pack);
}

pack = await context.models.constructPackageObjectFull(pack.content);
ctx.timecop.start("construct");
pack = await ctx.models.constructPackageObjectFull(pack.content);

if (params.engine !== false) {
// query.engine returns false if no valid query param is found.
// before using engineFilter we need to check the truthiness of it.

pack = await context.utils.engineFilter(pack, params.engine);
pack = await ctx.utils.engineFilter(pack, params.engine);
}
ctx.timecop.end("construct");

const sso = new context.sso();
const sso = new ctx.sso();

return sso.isOk().addContent(pack);
},
Expand Down
41 changes: 41 additions & 0 deletions src/models/timecop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

module.exports =
class Timecop {
constructor() {
this.timetable = {};
}

start(service) {
this.timetable[service] = {
start: performance.now(),
end: undefined,
duration: undefined
};
}

end(service) {
if (!this.timetable[service]) {
this.timetable[service] = {};
this.timetable[service].start = 0; // Wildly incorrect date, more likely
// to be caught rather than letting the time taken be 0ms
}
this.timetable[service].end = performance.now();
this.timetable[service].duration =
this.timetable[service].end -
this.timetable[service].start;
}

toHeader() {
let str = "";

for (const service in this.timetable) {
if (str.length > 0) {
str = str + ", ";
}

str = str + `${service};dur=${Number(this.timetable[service].duration).toFixed(2)}`;
}

return str;
}
}
37 changes: 36 additions & 1 deletion src/setupEndpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ const authLimit = rateLimit({
},
});

// TODO: Once all controllers are migrated to v2, or all endpoint tests are HTTP
// based, we can move this to `./context.js`, but until then unit tests rely on
// the structure of Context, and instead of changing it, we can define the builder here
const Timecop = require("./models/timecop.js");
const buildContext = (req, res, params) => {
return {
req: req,
res: res,
params: params,
timecop: new Timecop(),
...context,
callStack: new context.callStack(), // Put after spread operator on CTX so
// it overwrites the original callstack uninitialized class
};
};

// Set express defaults

app.set("trust proxy", true);
Expand All @@ -56,6 +72,7 @@ const endpointHandler = async function (node, req, res) {
await node.preLogic(req, res, context);
}

const sharedCtx = buildContext(req, res, params);
let obj;

try {
Expand All @@ -64,7 +81,16 @@ const endpointHandler = async function (node, req, res) {
// If it's a raw endpoint, they must handle all other steps manually
return;
} else {
obj = await node.logic(params, context);
switch(node.version) {
case 2:
obj = await node.logic(sharedCtx);
break;
case 1:
default:
// Previous default, implicit version 1 behavior
obj = await node.logic(params, context);
break;
}
}
} catch (err) {
// The main logic request has failed. We will generate our own return obj,
Expand All @@ -81,6 +107,15 @@ const endpointHandler = async function (node, req, res) {
await node.postLogic(req, res, context);
}

// Before handling our return check again for our node.version to check for
// extra steps
if (node.version === 2) {
// Server-Timing Header check
if (Object.keys(sharedCtx.timecop.timetable).length > 0) {
res.append("Server-Timing", sharedCtx.timecop.toHeader());
}
}

obj.addGoodStatus(node.endpoint.successStatus);

obj.handleReturnHTTP(req, res, context);
Expand Down
44 changes: 44 additions & 0 deletions tests/full/getPackagesPackageName.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const supertest = require("supertest");
const app = require("../../src/setupEndpoints.js");
const database = require("../../src/database/_export.js");
const genPackage = require("../helpers/package.jest.js");

describe("Behaves as expected", () => {
test("Returns 404 when a package doesn't exist", async () => {
const res = await supertest(app).get("/api/packages/anything");

expect(res).toHaveHTTPCode(404);
expect(res.body.message).toBe("Not Found");
});

test("Returns a package on success", async () => {
await database.insertNewPackage(
genPackage("https://github.com/confused-Techie/get-package-test", {
versions: [ "1.1.0", "1.0.0" ]
})
);

const res = await supertest(app).get("/api/packages/get-package-test");

expect(res).toHaveHTTPCode(200);
expect(res.body.name).toBe("get-package-test");
expect(res.body.owner).toBe("confused-Techie");

await database.removePackageByName("get-package-test", true);
});

test("Returns a bundled package without it existing in the database", async () => {
const res = await supertest(app).get("/api/packages/settings-view");

expect(res).toHaveHTTPCode(200);
expect(res.body.name).toBe("settings-view");
expect(res.body.owner).toBe("pulsar-edit");
expect(res.body.repository.url).toBe("https://github.com/pulsar-edit/pulsar");
});

test("Adheres to `Server-Timing` Specification", async () => {
const res = await supertest(app).get("/api/packages/i-dont-exist");

expect(res.headers["server-timing"]).toBeTypeof("string");
});
});
60 changes: 0 additions & 60 deletions tests/http/getPackagesPackageName.test.js

This file was deleted.