Merge pull request #20 from saleor/test-setup

Setup test with Vitest + example tests + CICD
This commit is contained in:
Krzysztof Wolski 2022-08-08 19:46:19 +02:00 committed by GitHub
commit 0524013a42
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 262 additions and 48 deletions

View file

@ -12,6 +12,7 @@
"ignorePatterns": ["pnpm-lock.yaml"],
"plugins": ["simple-import-sort", "@typescript-eslint"],
"rules": {
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
"quotes": ["error", "double"],
"react/react-in-jsx-scope": "off", // next does not require react imports
"import/extensions": "off", // file extension not required when importing

View file

@ -17,6 +17,22 @@ jobs:
- name: Check linters
run: pnpm lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2.2.1
with:
version: 6.19.1
- uses: actions/setup-node@v3
with:
node-version: "16"
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Test
run: pnpm test
build:
runs-on: ubuntu-latest
steps:

View file

@ -8,8 +8,7 @@
"prepublishOnly": "pnpm build",
"watch": "tsup-node src/* --format esm,cjs --dts --watch",
"build": "tsup-node src/* --format esm,cjs --dts && clear-package-json package.json -o dist/package.json --fields publishConfig",
"test": "uvu -r tsm spec",
"test-watch": "watchlist src spec -- pnpm test",
"test": "vitest",
"prepare": "husky install",
"lint": "prettier --loglevel warn --write . && eslint --fix ."
},
@ -41,7 +40,7 @@
"tsm": "^2.2.1",
"tsup": "^6.2.0",
"typescript": "^4.7.4",
"uvu": "^0.5.6",
"vitest": "^0.21.0",
"watchlist": "^0.3.1"
},
"lint-staged": {

View file

@ -23,7 +23,7 @@ specifiers:
tsm: ^2.2.1
tsup: ^6.2.0
typescript: ^4.7.4
uvu: ^0.5.6
vitest: ^0.21.0
watchlist: ^0.3.1
dependencies:
@ -51,7 +51,7 @@ devDependencies:
tsm: 2.2.1
tsup: 6.2.0_typescript@4.7.4
typescript: 4.7.4
uvu: 0.5.6
vitest: 0.21.0
watchlist: 0.3.1
packages:
@ -133,6 +133,16 @@ packages:
tslib: 2.4.0
dev: true
/@types/chai-subset/1.3.3:
resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
dependencies:
'@types/chai': 4.3.3
dev: true
/@types/chai/4.3.3:
resolution: {integrity: sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==}
dev: true
/@types/json-schema/7.0.11:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true
@ -366,6 +376,10 @@ packages:
es-shim-unscopables: 1.0.0
dev: true
/assertion-error/1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
dev: true
/ast-types-flow/0.0.7:
resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==}
dev: true
@ -435,6 +449,19 @@ packages:
engines: {node: '>=6'}
dev: true
/chai/4.3.6:
resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==}
engines: {node: '>=4'}
dependencies:
assertion-error: 1.1.0
check-error: 1.0.2
deep-eql: 3.0.1
get-func-name: 2.0.0
loupe: 2.3.4
pathval: 1.1.1
type-detect: 4.0.8
dev: true
/chalk/4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@ -443,6 +470,10 @@ packages:
supports-color: 7.2.0
dev: true
/check-error/1.0.2:
resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
dev: true
/chokidar/3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
@ -545,6 +576,13 @@ packages:
ms: 2.1.2
dev: true
/deep-eql/3.0.1:
resolution: {integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==}
engines: {node: '>=0.12'}
dependencies:
type-detect: 4.0.8
dev: true
/deep-is/0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
dev: true
@ -562,16 +600,6 @@ packages:
object-keys: 1.1.1
dev: true
/dequal/2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
dev: true
/diff/5.1.0:
resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
engines: {node: '>=0.3.1'}
dev: true
/dir-glob/3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@ -1281,6 +1309,10 @@ packages:
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
dev: true
/get-func-name/2.0.0:
resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
dev: true
/get-intrinsic/1.1.2:
resolution: {integrity: sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==}
dependencies:
@ -1635,11 +1667,6 @@ packages:
object.assign: 4.1.2
dev: true
/kleur/4.1.5:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
dev: true
/language-subtag-registry/0.3.22:
resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==}
dev: true
@ -1672,6 +1699,11 @@ packages:
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: true
/local-pkg/0.4.2:
resolution: {integrity: sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==}
engines: {node: '>=14'}
dev: true
/locate-path/2.0.0:
resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==}
engines: {node: '>=4'}
@ -1695,6 +1727,12 @@ packages:
js-tokens: 4.0.0
dev: true
/loupe/2.3.4:
resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==}
dependencies:
get-func-name: 2.0.0
dev: true
/lru-cache/6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
@ -1753,6 +1791,12 @@ packages:
thenify-all: 1.6.0
dev: true
/nanoid/3.3.4:
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
/natural-compare/1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: true
@ -1911,6 +1955,10 @@ packages:
engines: {node: '>=8'}
dev: true
/pathval/1.1.1:
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
dev: true
/picocolors/1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
@ -1940,6 +1988,15 @@ packages:
yaml: 1.10.2
dev: true
/postcss/8.4.16:
resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.4
picocolors: 1.0.0
source-map-js: 1.0.2
dev: true
/prelude-ls/1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@ -2054,13 +2111,6 @@ packages:
dependencies:
queue-microtask: 1.2.3
/sade/1.8.1:
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
engines: {node: '>=6'}
dependencies:
mri: 1.2.0
dev: true
/semver/6.3.0:
resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
hasBin: true
@ -2108,6 +2158,11 @@ packages:
engines: {node: '>=12'}
dev: true
/source-map-js/1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
dev: true
/source-map/0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
engines: {node: '>= 8'}
@ -2233,6 +2288,16 @@ packages:
globrex: 0.1.2
dev: true
/tinypool/0.2.4:
resolution: {integrity: sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ==}
engines: {node: '>=14.0.0'}
dev: true
/tinyspy/1.0.0:
resolution: {integrity: sha512-FI5B2QdODQYDRjfuLF+OrJ8bjWRMCXokQPcwKm0W3IzcbUmBNv536cQc7eXGoAuXphZwgx1DFbqImwzz08Fnhw==}
engines: {node: '>=14.0.0'}
dev: true
/to-regex-range/5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@ -2332,6 +2397,11 @@ packages:
prelude-ls: 1.2.1
dev: true
/type-detect/4.0.8:
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
engines: {node: '>=4'}
dev: true
/type-fest/0.20.2:
resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
engines: {node: '>=10'}
@ -2358,21 +2428,79 @@ packages:
punycode: 2.1.1
dev: true
/uvu/0.5.6:
resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==}
engines: {node: '>=8'}
hasBin: true
dependencies:
dequal: 2.0.3
diff: 5.1.0
kleur: 4.1.5
sade: 1.8.1
dev: true
/v8-compile-cache/2.3.0:
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
dev: true
/vite/3.0.4:
resolution: {integrity: sha512-NU304nqnBeOx2MkQnskBQxVsa0pRAH5FphokTGmyy8M3oxbvw7qAXts2GORxs+h/2vKsD+osMhZ7An6yK6F1dA==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
peerDependencies:
less: '*'
sass: '*'
stylus: '*'
terser: ^5.4.0
peerDependenciesMeta:
less:
optional: true
sass:
optional: true
stylus:
optional: true
terser:
optional: true
dependencies:
esbuild: 0.14.49
postcss: 8.4.16
resolve: 1.22.1
rollup: 2.76.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/vitest/0.21.0:
resolution: {integrity: sha512-+BQB2swk4wQdw5loOoL8esIYh/1ifAliuwj2HWHNE2F8SAl/jF7/aoCJBoXGSf/Ws19k3pH4NrWeVtcSwM0j2w==}
engines: {node: '>=v14.16.0'}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@vitest/browser': '*'
'@vitest/ui': '*'
c8: '*'
happy-dom: '*'
jsdom: '*'
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@vitest/browser':
optional: true
'@vitest/ui':
optional: true
c8:
optional: true
happy-dom:
optional: true
jsdom:
optional: true
dependencies:
'@types/chai': 4.3.3
'@types/chai-subset': 1.3.3
'@types/node': 18.0.4
chai: 4.3.6
debug: 4.3.4
local-pkg: 0.4.2
tinypool: 0.2.4
tinyspy: 1.0.0
vite: 3.0.4
transitivePeerDependencies:
- less
- sass
- stylus
- supports-color
- terser
dev: true
/watchlist/0.3.1:
resolution: {integrity: sha512-m5r4bzxJ9eg07TT/O0Q49imFPD45ZTuQ3kaHwSpUJj1QwVd3pzit4UYOmySdmAP5Egkz6mB6hcAPuPfhIbNo0g==}
engines: {node: '>=8'}

32
src/middleware.test.ts Normal file
View file

@ -0,0 +1,32 @@
import { Handler, Request } from "retes";
import { Response } from "retes/types";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { withBaseURL } from "./middleware";
const getMockEmptyResponse = async () => ({} as Response);
describe("middleware.test.ts", () => {
describe("withBaseURL", () => {
let mockHandlerFn: Handler = vi.fn(getMockEmptyResponse);
beforeEach(() => {
mockHandlerFn = vi.fn();
});
it("Adds base URL from request header to context and calls handler", async () => {
const mockRequest = {
context: {},
headers: {
host: "my-saleor-env.saleor.cloud",
"x-forwarded-proto": "https",
},
} as unknown as Request;
await withBaseURL(mockHandlerFn)(mockRequest);
expect(mockRequest.context.baseURL).toBe("https://my-saleor-env.saleor.cloud");
expect(mockHandlerFn).toHaveBeenCalledOnce();
});
});
});

View file

@ -5,15 +5,14 @@ import { Response } from "retes/response";
import { SALEOR_AUTHORIZATION_BEARER_HEADER, SALEOR_SIGNATURE_HEADER } from "./const";
import { getSaleorHeaders } from "./headers";
import { jwksUrl } from "./urls";
import { getJwksUrl } from "./urls";
export const withBaseURL: Middleware = (handler) => async (request) => {
const { host, "x-forwarded-proto": protocol = "http" } = request.headers;
request.context.baseURL = `${protocol}://${host}`;
const response = await handler(request);
return response;
return handler(request);
};
export const withSaleorDomainPresent: Middleware = (handler) => async (request) => {
@ -100,7 +99,7 @@ export const withWebhookSignatureVerified =
};
const remoteJwks = jose.createRemoteJWKSet(
new URL(jwksUrl(saleorDomain))
new URL(getJwksUrl(saleorDomain))
) as jose.FlattenedVerifyGetKey;
try {
@ -176,7 +175,7 @@ export const withJWTVerified =
}
try {
const JWKS = jose.createRemoteJWKSet(new URL(jwksUrl(domain)));
const JWKS = jose.createRemoteJWKSet(new URL(getJwksUrl(domain)));
await jose.jwtVerify(token, JWKS);
} catch (e) {
console.error(e);

26
src/urls.test.ts Normal file
View file

@ -0,0 +1,26 @@
import { describe, expect, test } from "vitest";
import { getGraphQLUrl, getJwksUrl } from "./urls";
describe("urls.ts", () => {
describe("jwksUrl function", () => {
test.each([
["localhost:8000", "http://localhost:8000/.well-known/jwks.json"],
[
"https://my-saleor.saleor.cloud",
"https://https://my-saleor.saleor.cloud/.well-known/jwks.json",
],
])("resolves %s to be %s", (input, expectedOutput) => {
expect(getJwksUrl(input)).toBe(expectedOutput);
});
});
describe("graphQLUrl function", () => {
test.each([
["localhost:8000", "http://localhost:8000/graphql/"],
["https://my-saleor.saleor.cloud", "https://https://my-saleor.saleor.cloud/graphql/"],
])("resolves %s to be %s", (input, expectedOutput) => {
expect(getGraphQLUrl(input)).toBe(expectedOutput);
});
});
});

View file

@ -1,8 +1,21 @@
const urlProtocol = (saleorDomain: string): string =>
/**
* TODO: Validate proper URL
*/
const resolveUrlProtocol = (saleorDomain: string): string =>
saleorDomain === "localhost:8000" ? "http" : "https";
export const jwksUrl = (saleorDomain: string): string =>
`${urlProtocol(saleorDomain)}://${saleorDomain}/.well-known/jwks.json`;
export const getJwksUrl = (saleorDomain: string): string =>
`${resolveUrlProtocol(saleorDomain)}://${saleorDomain}/.well-known/jwks.json`;
export const graphQLUrl = (saleorDomain: string): string =>
`${urlProtocol(saleorDomain)}://${saleorDomain}/graphql/`;
export const getGraphQLUrl = (saleorDomain: string): string =>
`${resolveUrlProtocol(saleorDomain)}://${saleorDomain}/graphql/`;
/**
* @deprecated Remove in v1, left for compatibility
*/
export const jwksUrl = getJwksUrl;
/**
* @deprecated Remove in v1, left for compatibility
*/
export const graphQLUrl = getGraphQLUrl;