Skip to content

Commit c8b96e5

Browse files
committed
feat: countDownload api and tests
1 parent c09f2e6 commit c8b96e5

File tree

9 files changed

+253
-26
lines changed

9 files changed

+253
-26
lines changed

.nycrc.unit.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"**/*.spec.js",
1717
"src/index.js",
1818
"src/server.js",
19-
"src/www/bootstrap/*"
19+
"src/www/bootstrap/*",
20+
"src/www/publish/*"
2021
],
2122
"branches": 80,
2223
"lines": 80,

src/api/countDownload.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Refer https://json-schema.org/understanding-json-schema/index.html
2+
import {EXTENSIONS_DETAILS_TABLE, FIELD_EXTENSION_ID} from "../constants.js";
3+
import db from "../db.js";
4+
5+
const schema = {
6+
schema: {
7+
querystring: {
8+
type: 'object',
9+
required: ['extensionName', 'extensionVersion'],
10+
properties: {
11+
extensionName: {
12+
type: 'string',
13+
minLength: 1,
14+
maxLength: 256
15+
},
16+
extensionVersion: {
17+
type: 'string',
18+
minLength: 1,
19+
maxLength: 32
20+
}
21+
}
22+
},
23+
response: {
24+
200: { //HTTP_STATUS_CODES.OK
25+
type: 'object',
26+
required: ['message'],
27+
properties: {
28+
message: {type: 'string'}
29+
}
30+
}
31+
}
32+
}
33+
};
34+
35+
export function getCountDownloadSchema() {
36+
return schema;
37+
}
38+
39+
async function _getRegistryPkgJSON(extensionName) {
40+
const queryObj = {};
41+
queryObj[FIELD_EXTENSION_ID] = extensionName;
42+
let registryPKGJSON = await db.getFromIndex(EXTENSIONS_DETAILS_TABLE, queryObj);
43+
if(!registryPKGJSON.isSuccess){
44+
// unexpected error
45+
throw new Error("Error getting extensionPKG details from db: " + extensionName);
46+
}
47+
if(registryPKGJSON.documents.length === 1){
48+
let existingRegistryDocumentId = registryPKGJSON.documents[0].documentId;
49+
delete registryPKGJSON.documents[0].documentId;
50+
return {
51+
existingRegistryDocumentId,
52+
registryPKGJSON: registryPKGJSON.documents[0]
53+
};
54+
}
55+
return null;
56+
}
57+
58+
/**
59+
* Checks if the given version is in the list of published versions in the registry entry.
60+
* @param registryPKGJSON
61+
* @param version
62+
* @private
63+
*/
64+
function _isPublishedVersion(registryPKGJSON, version) {
65+
let versions = [];
66+
versions.push(registryPKGJSON.metadata.version);
67+
for(let versionInfo of registryPKGJSON.versions){
68+
versions.push(versionInfo.version);
69+
}
70+
return versions.includes(version);
71+
}
72+
73+
export async function countDownload(request, _reply) {
74+
const extensionName = request.query.extensionName; // extension.name
75+
const extensionVersion = request.query.extensionVersion; // 1.0.2
76+
let registryEntry = await _getRegistryPkgJSON(extensionName);
77+
if(!registryEntry) {
78+
throw new Error("No such extension");
79+
}
80+
if(!_isPublishedVersion(registryEntry.registryPKGJSON, extensionVersion)) {
81+
throw new Error("No such extension version");
82+
}
83+
let status = await db.mathAdd(EXTENSIONS_DETAILS_TABLE, registryEntry.existingRegistryDocumentId, {
84+
totalDownloads: 1
85+
});
86+
if(!status.isSuccess) {
87+
throw new Error("Could not increment download count.");
88+
}
89+
const response = {
90+
message: `Done`
91+
};
92+
return response;
93+
}

src/api/getGithubReleaseStatus.js

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,22 +59,24 @@ export async function getGithubReleaseStatus(request, _reply) {
5959
throw new Error("Error getting release details from db: " + releaseRef);
6060
}
6161
existingRelease = existingRelease.documents.length === 1 ? existingRelease.documents[0] : null;
62+
if (!existingRelease) {
63+
throw new Error("Release not found. IF this is a recent release," +
64+
" please wait for 1 minute before checking again.");
65+
}
66+
6267
const response = {
63-
status: "NO_SUCH_RELEASE",
64-
errors: [`Release not found. IF this is a recent release, please wait for 1 minute before checking again.`]
68+
published: existingRelease.published || false,
69+
status: existingRelease.status,
70+
errors: existingRelease.errors,
71+
githubIssue: "" + existingRelease.githubIssue,
72+
lastUpdatedDateUTC: existingRelease.lastUpdatedDateUTC
6573
};
66-
if (existingRelease) {
67-
response.published = existingRelease.published || false;
68-
response.status = existingRelease.status;
69-
response.errors = existingRelease.errors;
70-
response.githubIssue = "" + existingRelease.githubIssue;
71-
response.lastUpdatedDateUTC = existingRelease.lastUpdatedDateUTC;
72-
if(existingRelease.publishedExtensionName){
73-
response.publishedExtensionName = existingRelease.publishedExtensionName;
74-
}
75-
if(existingRelease.publishedVersion){
76-
response.publishedVersion = existingRelease.publishedVersion;
77-
}
74+
if(existingRelease.publishedExtensionName){
75+
response.publishedExtensionName = existingRelease.publishedExtensionName;
76+
}
77+
if(existingRelease.publishedVersion){
78+
response.publishedVersion = existingRelease.publishedVersion;
7879
}
80+
7981
return response;
8082
}

src/db.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ let db = {
99
update: coco.update,
1010
getFromIndex: coco.getFromIndex,
1111
query: coco.query,
12-
close: coco.close
12+
close: coco.close,
13+
mathAdd: coco.mathAdd
1314
};
1415

1516
export default db;

src/server.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import path from 'path';
3131
import { fileURLToPath } from 'url';
3232
import {initGitHubClient} from "./github.js";
3333
import {getGetGithubReleaseStatusSchema, getGithubReleaseStatus} from "./api/getGithubReleaseStatus.js";
34+
import {getCountDownloadSchema, countDownload} from "./api/countDownload.js";
3435

3536
const __filename = fileURLToPath(import.meta.url);
3637
const __dirname = path.dirname(__filename);
@@ -96,6 +97,13 @@ server.get('/getGithubReleaseStatus', getGetGithubReleaseStatusSchema(), functio
9697
return getGithubReleaseStatus(request, reply);
9798
});
9899

100+
// public countDownload api
101+
addUnAuthenticatedAPI('/countDownload');
102+
server.get('/countDownload', getCountDownloadSchema(), function (request, reply) {
103+
return countDownload(request, reply);
104+
});
105+
106+
99107
// An authenticated version of the hello api
100108
server.get('/helloAuth', getHelloSchema(), function (request, reply) {
101109
return hello(request, reply);

src/www/publish/script.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,15 @@ function retryRelease() {
2323
}
2424

2525
function showStatus() {
26+
if(!owner || !repo || !tag){
27+
document.getElementById("releaseCheckStatus").textContent =
28+
"No such release. Please check if you provided the correct Github Owner/Repo/Release tag.";
29+
return;
30+
}
2631
fetch(`../../getGithubReleaseStatus?owner=${owner}&repo=${repo}&tag=${tag}`).then(async result=>{
32+
if(result.status !== 200){
33+
throw new Error(`getGithubReleaseStatus returned ${result.status}`);
34+
}
2735
let releaseDetail = await result.json();
2836
console.log(releaseDetail);
2937
if(releaseDetail.published) {
@@ -58,7 +66,12 @@ function showStatus() {
5866
}
5967
}
6068
}).catch((err)=>{
69+
document.getElementById("releaseCheckingSection").classList.remove("hidden");
70+
document.getElementById("releaseSuccessSection").classList.add("hidden");
71+
document.getElementById("releaseFailedSection").classList.add("hidden");
6172
console.error("Error while fetching release status", err);
62-
document.getElementById("releaseCheckStatus").textContent = "Could not retrieve release status.";
73+
document.getElementById("releaseCheckStatus").innerHTML = "Could not retrieve release status for: </br>" +
74+
`<a href='https://github.com/${owner}/${repo}/releases/tag/${tag}'>GitHub release ${owner}/${repo}/${tag}</a>.` +
75+
"</br>Are you sure that the release exists?";
6376
});
6477
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*global describe, it, before, after, beforeEach*/
2+
import mockedFunctions from "../setupMocks.js";
3+
import * as chai from 'chai';
4+
import {countDownload, getCountDownloadSchema} from "../../../src/api/countDownload.js";
5+
import {getSimpleGetReply, getSimpleGETRequest} from '../data/simple-request.js';
6+
import {REGISTRY_PACKAGE_JSON, VALID_PACKAGE_JSON} from '../data/packagejson.js';
7+
import Ajv from "ajv";
8+
import db from "../../../src/db.js";
9+
10+
export const AJV = new Ajv();
11+
12+
13+
let expect = chai.expect;
14+
15+
describe('unit Tests for countDownload api', function () {
16+
let savedGetFromIndex, savedMathAdd;
17+
before(()=>{
18+
savedGetFromIndex = db.getFromIndex;
19+
savedMathAdd = db.mathAdd;
20+
});
21+
after(()=>{
22+
db.getFromIndex = savedGetFromIndex;
23+
db.mathAdd = savedMathAdd;
24+
});
25+
beforeEach(function () {
26+
db.getFromIndex = function (_tableName) {
27+
return {
28+
isSuccess: true,
29+
documents: [REGISTRY_PACKAGE_JSON]};
30+
};
31+
db.mathAdd = function (_tableName) {
32+
return {
33+
isSuccess: true
34+
};
35+
};
36+
});
37+
function _getRequest(extensionName="ext.name", extensionVersion ="0.0.1") {
38+
let request = getSimpleGETRequest();
39+
request.query.extensionName = extensionName;
40+
request.query.extensionVersion = extensionVersion;
41+
return request;
42+
}
43+
44+
it('should countDownload throw if db get failed', async function () {
45+
db.getFromIndex = function (_tableName) {
46+
return {isSuccess: false};
47+
};
48+
let error;
49+
try{
50+
await countDownload(_getRequest(), getSimpleGetReply());
51+
} catch(e){
52+
error = e;
53+
}
54+
expect(error.message).eql("Error getting extensionPKG details from db: ext.name");
55+
});
56+
it('should countDownload throw if no such extension', async function () {
57+
db.getFromIndex = function (_tableName) {
58+
return {isSuccess: true, documents:[]};
59+
};
60+
let error;
61+
try{
62+
await countDownload(_getRequest(), getSimpleGetReply());
63+
} catch(e){
64+
error = e;
65+
}
66+
expect(error.message).eql("No such extension");
67+
});
68+
it('should countDownload throw if no such extension version', async function () {
69+
let error;
70+
try{
71+
await countDownload(_getRequest("dd", "1.2.3"), getSimpleGetReply());
72+
} catch(e){
73+
error = e;
74+
}
75+
expect(error.message).eql("No such extension version");
76+
});
77+
78+
it('should countDownload increment download', async function () {
79+
let helloResponse = await countDownload(_getRequest(), getSimpleGetReply());
80+
expect(helloResponse).eql({
81+
"message": "Done"
82+
});
83+
});
84+
85+
it('should countDownload failed is failed to increment in db', async function () {
86+
db.mathAdd = function (_tableName) {return {isSuccess: false};};
87+
let error;
88+
try{
89+
await countDownload(_getRequest(), getSimpleGetReply());
90+
} catch(e){
91+
error = e;
92+
}
93+
expect(error.message).eql("Could not increment download count.");
94+
});
95+
96+
it('should validate schemas for sample request/responses', async function () {
97+
let request = _getRequest();
98+
// request
99+
const requestValidator = AJV.compile(getCountDownloadSchema().schema.querystring);
100+
expect(requestValidator(request.query)).to.be.true;
101+
// remove a required field
102+
delete request.query.extensionName;
103+
expect(requestValidator(request.query)).to.be.false;
104+
// response
105+
const successResponseValidator = AJV.compile(getCountDownloadSchema().schema.response["200"]);
106+
let response = await countDownload(_getRequest(), getSimpleGetReply());
107+
expect(successResponseValidator(response)).to.be.true;
108+
});
109+
});

test/unit/api/getGithubReleaseStatus.spec.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ describe('unit Tests for getGithubReleaseStatus api', function () {
2020
return request;
2121
}
2222

23-
it('should getGithubReleaseStatus return no such release if no release', async function () {
24-
let helloResponse = await getGithubReleaseStatus(_getRequest(), getSimpleGetReply());
25-
expect(helloResponse).eql({
26-
"errors": [
27-
"Release not found. IF this is a recent release, please wait for 1 minute before checking again."
28-
],
29-
"status": "NO_SUCH_RELEASE"
30-
});
23+
it('should getGithubReleaseStatus throw if no release', async function () {
24+
let error;
25+
try{
26+
await getGithubReleaseStatus(_getRequest(), getSimpleGetReply());
27+
} catch(e){
28+
error = e;
29+
}
30+
expect(error.message).eql("Release not found. IF this is a recent release, please wait for 1 minute before checking again.");
3131
});
3232

3333
it('should getGithubReleaseStatus return existing release details', async function () {

test/unit/setupMocks.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ function _setup() {
6262
isSuccess: true
6363
};
6464
};
65-
db.createDb = db.createTable = db.createIndex = db.put = db.update = successMock;
65+
db.createDb = db.createTable = db.createIndex = db.put = db.update = db.mathAdd = successMock;
6666
db.getFromIndex = (...args)=>{
6767
return {
6868
isSuccess: true,

0 commit comments

Comments
 (0)