Compare commits

..
Author SHA1 Message Date
Anurag 8667ea9908 Merge branch 'master' of https://github.com/anuraghazra/github-readme-stats 2023-01-22 00:36:12 +05:30
rickstaa fbb49e3c80 test: update e2e test data 2023-01-21 12:15:58 +01:00
rickstaa eab1402417 test: fix e2e test data 2023-01-21 11:37:41 +01:00
rickstaa cfa84232e2 test: update snapshots 2023-01-21 11:32:25 +01:00
rickstaa d5d1f44e1c test: fix e2e tests 2023-01-21 11:31:42 +01:00
14 changed files with 78 additions and 168 deletions
+3 -25
View File
@@ -7,7 +7,6 @@ import {
parseBoolean, parseBoolean,
renderError, renderError,
} from "../src/common/utils.js"; } from "../src/common/utils.js";
import { HttpException } from "../src/common/exceptions.js";
import { fetchStats } from "../src/fetchers/stats-fetcher.js"; import { fetchStats } from "../src/fetchers/stats-fetcher.js";
import { isLocaleAvailable } from "../src/translations.js"; import { isLocaleAvailable } from "../src/translations.js";
@@ -57,7 +56,7 @@ export default async (req, res) => {
); );
const cacheSeconds = clampValue( const cacheSeconds = clampValue(
parseInt(cache_seconds || CONSTANTS.CARD_CACHE_SECONDS, 10), parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10),
CONSTANTS.FOUR_HOURS, CONSTANTS.FOUR_HOURS,
CONSTANTS.ONE_DAY, CONSTANTS.ONE_DAY,
); );
@@ -66,8 +65,7 @@ export default async (req, res) => {
"Cache-Control", "Cache-Control",
`max-age=${ `max-age=${
cacheSeconds / 2 cacheSeconds / 2
}, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}, }, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
stale-if-error=${CONSTANTS.ONE_HOUR}`,
); );
return res.send( return res.send(
@@ -95,27 +93,7 @@ export default async (req, res) => {
}), }),
); );
} catch (err) { } catch (err) {
// Throw error if REST and GraphQL API calls fail. This way we can return a cached res.setHeader("Cache-Control", `no-cache, no-store, must-revalidate`); // Don't cache error responses.
if (err instanceof HttpException) {
// throw err;
// throw new Error(err.message);
// return res.status(404).;
// return {statusCode: 404, body: err.message}
return res
.status(500)
.send(
renderError(err.errors[0].message, err.errors[0].secondaryMessage),
);
}
// Cache the error response less frequently.
res.setHeader(
"Cache-Control",
`max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${
CONSTANTS.ERROR_CACHE_SECONDS
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}
stale-if-error=${CONSTANTS.ONE_HOUR}`,
);
return res.send(renderError(err.message, err.secondaryMessage)); return res.send(renderError(err.message, err.secondaryMessage));
} }
}; };
+2 -7
View File
@@ -40,7 +40,7 @@ export default async (req, res) => {
const repoData = await fetchRepo(username, repo); const repoData = await fetchRepo(username, repo);
let cacheSeconds = clampValue( let cacheSeconds = clampValue(
parseInt(cache_seconds || CONSTANTS.CARD_CACHE_SECONDS, 10), parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10),
CONSTANTS.FOUR_HOURS, CONSTANTS.FOUR_HOURS,
CONSTANTS.ONE_DAY, CONSTANTS.ONE_DAY,
); );
@@ -80,12 +80,7 @@ export default async (req, res) => {
}), }),
); );
} catch (err) { } catch (err) {
res.setHeader( res.setHeader("Cache-Control", `no-cache, no-store, must-revalidate`); // Don't cache error responses.
"Cache-Control",
`max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${
CONSTANTS.ERROR_CACHE_SECONDS
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
); // Cache the error response less frequently.
return res.send(renderError(err.message, err.secondaryMessage)); return res.send(renderError(err.message, err.secondaryMessage));
} }
}; };
+2 -7
View File
@@ -48,7 +48,7 @@ export default async (req, res) => {
); );
const cacheSeconds = clampValue( const cacheSeconds = clampValue(
parseInt(cache_seconds || CONSTANTS.CARD_CACHE_SECONDS, 10), parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10),
CONSTANTS.FOUR_HOURS, CONSTANTS.FOUR_HOURS,
CONSTANTS.ONE_DAY, CONSTANTS.ONE_DAY,
); );
@@ -80,12 +80,7 @@ export default async (req, res) => {
}), }),
); );
} catch (err) { } catch (err) {
res.setHeader( res.setHeader("Cache-Control", `no-cache, no-store, must-revalidate`); // Don't cache error responses.
"Cache-Control",
`max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${
CONSTANTS.ERROR_CACHE_SECONDS
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
); // Cache the error response less frequently.
return res.send(renderError(err.message, err.secondaryMessage)); return res.send(renderError(err.message, err.secondaryMessage));
} }
}; };
+6 -7
View File
@@ -43,11 +43,15 @@ export default async (req, res) => {
const stats = await fetchWakatimeStats({ username, api_domain, range }); const stats = await fetchWakatimeStats({ username, api_domain, range });
let cacheSeconds = clampValue( let cacheSeconds = clampValue(
parseInt(cache_seconds || CONSTANTS.CARD_CACHE_SECONDS, 10), parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10),
CONSTANTS.FOUR_HOURS, CONSTANTS.FOUR_HOURS,
CONSTANTS.ONE_DAY, CONSTANTS.ONE_DAY,
); );
if (!cache_seconds) {
cacheSeconds = CONSTANTS.FOUR_HOURS;
}
res.setHeader( res.setHeader(
"Cache-Control", "Cache-Control",
`max-age=${ `max-age=${
@@ -76,12 +80,7 @@ export default async (req, res) => {
}), }),
); );
} catch (err) { } catch (err) {
res.setHeader( res.setHeader("Cache-Control", `no-cache, no-store, must-revalidate`); // Don't cache error responses.
"Cache-Control",
`max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${
CONSTANTS.ERROR_CACHE_SECONDS
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
); // Cache the error response less frequently.
return res.send(renderError(err.message, err.secondaryMessage)); return res.send(renderError(err.message, err.secondaryMessage));
} }
}; };
-82
View File
@@ -1,82 +0,0 @@
/**
* @file GRS Exception/Errors.
*/
const SECONDARY_ERROR_MESSAGES = {
MAX_RETRY:
"Please add an env variable called PAT_1 with your github token in vercel",
USER_NOT_FOUND: "Make sure the provided username is not an organization",
GRAPHQL_ERROR: "Please try again later",
};
/**
* Custom error class to handle custom GRS errors.
*
* @extends Error
*/
class CustomError extends Error {
/**
* @param {string} message Error message.
* @param {string} type Error type.
*/
constructor(message, type) {
super(message);
this.type = type;
this.secondaryMessage = SECONDARY_ERROR_MESSAGES[type] || type;
}
static MAX_RETRY = "MAX_RETRY";
static USER_NOT_FOUND = "USER_NOT_FOUND";
static GRAPHQL_ERROR = "GRAPHQL_ERROR";
}
/**
* Missing query parameter class.
*
* @extends Error
*/
class MissingParamError extends Error {
/**
* @param {string[]} missedParams
* @param {string?=} secondaryMessage
*/
constructor(missedParams, secondaryMessage) {
const msg = `Missing params ${missedParams
.map((p) => `"${p}"`)
.join(", ")} make sure you pass the parameters in URL`;
super(msg);
this.missedParams = missedParams;
this.secondaryMessage = secondaryMessage;
}
}
/**
* HttpException class.
*
* @extends Error
*/
class HttpException extends Error {
/**
* Create a HttpException.
*
* @param {number} statusCode - The status code.
* @param {string} message - The error message.
* @param {string[]} errors - The errors.
*/
constructor(
statusCode,
message = "Exception occurred during the processing of HTTP requests.",
errors = [],
) {
super(message);
this.statusCode = statusCode;
this.errors = errors;
}
}
export {
SECONDARY_ERROR_MESSAGES,
CustomError,
MissingParamError,
HttpException,
};
+2 -1
View File
@@ -21,9 +21,10 @@ export {
wrapTextMultiline, wrapTextMultiline,
logger, logger,
CONSTANTS, CONSTANTS,
CustomError,
MissingParamError,
measureText, measureText,
lowercaseTrim, lowercaseTrim,
chunkArray, chunkArray,
parseEmojis, parseEmojis,
} from "./utils.js"; } from "./utils.js";
export { CustomError, MissingParamError, HttpException } from "./exceptions.js";
+4 -13
View File
@@ -1,8 +1,4 @@
import { logger } from "./utils.js"; import { CustomError, logger } from "./utils.js";
import {
CustomError,
HttpException,
} from "./exceptions.js";
// Script variables. // Script variables.
const PATs = Object.keys(process.env).filter((key) => const PATs = Object.keys(process.env).filter((key) =>
@@ -20,13 +16,8 @@ const RETRIES = PATs ? PATs : 7;
* @returns Promise<retryer> * @returns Promise<retryer>
*/ */
const retryer = async (fetcher, variables, retries = 0) => { const retryer = async (fetcher, variables, retries = 0) => {
// if (retries > RETRIES) { if (retries > RETRIES) {
if (true) { // FIXME: Test out error throw new CustomError("Maximum retries exceeded", CustomError.MAX_RETRY);
throw new HttpException(
statusCode=500,
message=`Max GraphQL retries exceeded. Please add an env variable called PAT_1 with your github token in vercel.`,
errors=[new CustomError("Maximum retries exceeded", CustomError.MAX_RETRY)]
);
} }
try { try {
// try to fetch with the first token since RETRIES is 0 index i'm adding +1 // try to fetch with the first token since RETRIES is 0 index i'm adding +1
@@ -69,5 +60,5 @@ const retryer = async (fetcher, variables, retries = 0) => {
} }
}; };
export { retryer, RETRIES }; export { retryer };
export default retryer; export default retryer;
+49 -12
View File
@@ -22,7 +22,8 @@ const renderError = (message, secondaryMessage = "") => {
.small { font: 600 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: #252525 } .small { font: 600 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: #252525 }
.gray { fill: #858585 } .gray { fill: #858585 }
</style> </style>
<rect x="0.5" y="0.5" width="${ERROR_CARD_LENGTH - 1 <rect x="0.5" y="0.5" width="${
ERROR_CARD_LENGTH - 1
}" height="99%" rx="4.5" fill="#FFFEFE" stroke="#E4E2E2"/> }" height="99%" rx="4.5" fill="#FFFEFE" stroke="#E4E2E2"/>
<text x="25" y="45" class="text">Something went wrong! file an issue at https://tiny.one/readme-stats</text> <text x="25" y="45" class="text">Something went wrong! file an issue at https://tiny.one/readme-stats</text>
<text data-testid="message" x="25" y="55" class="text small"> <text data-testid="message" x="25" y="55" class="text small">
@@ -287,28 +288,62 @@ const wrapTextMultiline = (text, width = 59, maxLines = 3) => {
return multiLineText; return multiLineText;
}; };
const noop = () => { }; const noop = () => {};
// return console instance based on the environment // return console instance based on the environment
const logger = const logger =
process.env.NODE_ENV !== "test" ? console : { log: noop, error: noop }; process.env.NODE_ENV !== "test" ? console : { log: noop, error: noop };
// Cache settings.
const CARD_CACHE_SECONDS = 14400;
const ERROR_CACHE_SECONDS = 600;
const CONSTANTS = { const CONSTANTS = {
ONE_MINUTE: 60,
FIVE_MINUTES: 300,
TEN_MINUTES: 600,
FIFTEEN_MINUTES: 900,
THIRTY_MINUTES: 1800, THIRTY_MINUTES: 1800,
TWO_HOURS: 7200, TWO_HOURS: 7200,
FOUR_HOURS: 14400, FOUR_HOURS: 14400,
ONE_DAY: 86400, ONE_DAY: 86400,
CARD_CACHE_SECONDS: CARD_CACHE_SECONDS,
ERROR_CACHE_SECONDS: ERROR_CACHE_SECONDS,
}; };
const SECONDARY_ERROR_MESSAGES = {
MAX_RETRY:
"Please add an env variable called PAT_1 with your github token in vercel",
USER_NOT_FOUND: "Make sure the provided username is not an organization",
GRAPHQL_ERROR: "Please try again later",
};
/**
* Custom error class to handle custom GRS errors.
*/
class CustomError extends Error {
/**
* @param {string} message Error message.
* @param {string} type Error type.
*/
constructor(message, type) {
super(message);
this.type = type;
this.secondaryMessage = SECONDARY_ERROR_MESSAGES[type] || type;
}
static MAX_RETRY = "MAX_RETRY";
static USER_NOT_FOUND = "USER_NOT_FOUND";
static GRAPHQL_ERROR = "GRAPHQL_ERROR";
}
/**
* Missing query parameter class.
*/
class MissingParamError extends Error {
/**
* @param {string[]} missedParams
* @param {string?=} secondaryMessage
*/
constructor(missedParams, secondaryMessage) {
const msg = `Missing params ${missedParams
.map((p) => `"${p}"`)
.join(", ")} make sure you pass the parameters in URL`;
super(msg);
this.missedParams = missedParams;
this.secondaryMessage = secondaryMessage;
}
}
/** /**
* Retrieve text length. * Retrieve text length.
* *
@@ -406,6 +441,8 @@ export {
wrapTextMultiline, wrapTextMultiline,
logger, logger,
CONSTANTS, CONSTANTS,
CustomError,
MissingParamError,
measureText, measureText,
lowercaseTrim, lowercaseTrim,
chunkArray, chunkArray,
+1 -2
View File
@@ -1,7 +1,6 @@
// @ts-check // @ts-check
import { retryer } from "../common/retryer.js"; import { retryer } from "../common/retryer.js";
import { request } from "../common/utils.js"; import { MissingParamError, request } from "../common/utils.js";
import { MissingParamError } from "../common/exceptions.js";
/** /**
* Repo data fetcher. * Repo data fetcher.
+2 -1
View File
@@ -5,11 +5,12 @@ import githubUsernameRegex from "github-username-regex";
import { calculateRank } from "../calculateRank.js"; import { calculateRank } from "../calculateRank.js";
import { retryer } from "../common/retryer.js"; import { retryer } from "../common/retryer.js";
import { import {
CustomError,
logger, logger,
MissingParamError,
request, request,
wrapTextMultiline, wrapTextMultiline,
} from "../common/utils.js"; } from "../common/utils.js";
import { CustomError, MissingParamError } from "../common/exceptions.js";
dotenv.config(); dotenv.config();
+2 -1
View File
@@ -1,11 +1,12 @@
// @ts-check // @ts-check
import { retryer } from "../common/retryer.js"; import { retryer } from "../common/retryer.js";
import { import {
CustomError,
logger, logger,
MissingParamError,
request, request,
wrapTextMultiline, wrapTextMultiline,
} from "../common/utils.js"; } from "../common/utils.js";
import { CustomError, MissingParamError } from "../common/exceptions.js";
/** /**
* Top languages fetcher object. * Top languages fetcher object.
+1 -1
View File
@@ -1,5 +1,5 @@
import axios from "axios"; import axios from "axios";
import { MissingParamError } from "../common/exceptions.js"; import { MissingParamError } from "../common/utils.js";
/** /**
* WakaTime data fetcher. * WakaTime data fetcher.
+2 -7
View File
@@ -171,18 +171,13 @@ describe("Test /api/", () => {
]); ]);
}); });
it("should set shorter cache when error", async () => { it("should not store cache when error", async () => {
const { req, res } = faker({}, error); const { req, res } = faker({}, error);
await api(req, res); await api(req, res);
expect(res.setHeader.mock.calls).toEqual([ expect(res.setHeader.mock.calls).toEqual([
["Content-Type", "image/svg+xml"], ["Content-Type", "image/svg+xml"],
[ ["Cache-Control", `no-cache, no-store, must-revalidate`],
"Cache-Control",
`max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${
CONSTANTS.ERROR_CACHE_SECONDS
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
],
]); ]);
}); });
+2 -2
View File
@@ -1,6 +1,6 @@
import { jest } from "@jest/globals"; import { jest } from "@jest/globals";
import "@testing-library/jest-dom"; import "@testing-library/jest-dom";
import { retryer, RETRIES } from "../src/common/retryer.js"; import { retryer } from "../src/common/retryer.js";
import { logger } from "../src/common/utils.js"; import { logger } from "../src/common/utils.js";
const fetcher = jest.fn((variables, token) => { const fetcher = jest.fn((variables, token) => {
@@ -45,7 +45,7 @@ describe("Test Retryer", () => {
try { try {
res = await retryer(fetcherFail, {}); res = await retryer(fetcherFail, {});
} catch (err) { } catch (err) {
expect(fetcherFail).toBeCalledTimes(RETRIES + 1); expect(fetcherFail).toBeCalledTimes(8);
expect(err.message).toBe("Maximum retries exceeded"); expect(err.message).toBe("Maximum retries exceeded");
} }
}); });