From e7fbd548926e76c58525d68de8771b7b2e1646a7 Mon Sep 17 00:00:00 2001 From: Hani Husamuddin Date: Tue, 31 Aug 2021 20:46:32 +0700 Subject: [PATCH 01/11] feat: init fetch-database.ts --- etc/fetchers/fetch-database.ts | 73 ++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 etc/fetchers/fetch-database.ts diff --git a/etc/fetchers/fetch-database.ts b/etc/fetchers/fetch-database.ts new file mode 100644 index 000000000..615c84e28 --- /dev/null +++ b/etc/fetchers/fetch-database.ts @@ -0,0 +1,73 @@ +import fs from "fs"; +import path from "path"; +import cheerio from "cheerio"; +import fetch from "cross-fetch"; +import { allIsEmptyString, getKebabCase } from "../../lib/string-utils"; +import { contactReducer } from "./utils"; + +export async function fetchDatabase() { + const source = await fetch("https://kcov.id/wbw-sheets"); + const $ = cheerio.load(await source.text()); + + const colMap: Record = {}; + + const sheetList = $("#sheet-menu > li") + .map((_, li) => { + const sheetId = ($(li).attr("id") as string).replace("sheet-button-", ""); + const sheetName = $(li).text(); + const sheetColumns = $(`#${sheetId} tbody > tr:nth-child(1)`) + .find("td") + .map((colIndex, td) => { + colMap[colIndex] = $(td).text(); + return { + name: $(td).text(), + index: colIndex, + }; + }) + .toArray() + .filter((col) => col.name.trim().length !== 0); + const sheetRows = $(`#${sheetId} tbody > tr`) + .map((rowIndex, tr) => { + if (rowIndex === 0) { + return []; + } + return [ + $(tr) + .find("td") + .map((colIndex, td) => { + if (colMap[colIndex]) { + // Kebutuhan, Keterangan, Lokasi, & Penyedia aren't supposed to be linked + if (colIndex < 5) { + return $(td).text().trim(); + } else { + return ($(td).html() as string).trim(); + } + } + return ""; + }) + .toArray(), + ]; + }) + .toArray() + .filter((row) => !allIsEmptyString(row)); + + return { + id: sheetId, + name: sheetName, + slug: getKebabCase(sheetName), + data: sheetRows.map((row, rowIndex) => { + return sheetColumns.reduce(contactReducer(row), { + id: rowIndex.toString(), + }); + }), + }; + }) + .toArray(); + + sheetList.shift(); + + fs.writeFileSync( + path.resolve(__dirname, "../../data/wbw-sheets.json"), + JSON.stringify(sheetList), + ); +} From 6c4c2f2e8514d8d6016e8cd2a53e0ce90b0ac72e Mon Sep 17 00:00:00 2001 From: Hani Husamuddin Date: Wed, 1 Sep 2021 14:09:13 +0700 Subject: [PATCH 02/11] refactor: change url endpoint, remove --- etc/fetchers/fetch-database.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/etc/fetchers/fetch-database.ts b/etc/fetchers/fetch-database.ts index 615c84e28..6185edbe5 100644 --- a/etc/fetchers/fetch-database.ts +++ b/etc/fetchers/fetch-database.ts @@ -6,7 +6,7 @@ import { allIsEmptyString, getKebabCase } from "../../lib/string-utils"; import { contactReducer } from "./utils"; export async function fetchDatabase() { - const source = await fetch("https://kcov.id/wbw-sheets"); + const source = await fetch("https://kcov.id/wbw-database"); const $ = cheerio.load(await source.text()); const colMap: Record = {}; @@ -64,10 +64,8 @@ export async function fetchDatabase() { }) .toArray(); - sheetList.shift(); - fs.writeFileSync( - path.resolve(__dirname, "../../data/wbw-sheets.json"), + path.resolve(__dirname, "../../data/wbw-database.json"), JSON.stringify(sheetList), ); } From 81fd42508531be8d42a242643cdfbcdf3312ce5d Mon Sep 17 00:00:00 2001 From: Hani Husamuddin Date: Wed, 1 Sep 2021 14:10:18 +0700 Subject: [PATCH 03/11] refactor: add fetchDatabase fn to `fetch-wbw.ts` --- etc/fetchers/fetch-wbw.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/etc/fetchers/fetch-wbw.ts b/etc/fetchers/fetch-wbw.ts index 076a21104..f87df969b 100644 --- a/etc/fetchers/fetch-wbw.ts +++ b/etc/fetchers/fetch-wbw.ts @@ -1,6 +1,7 @@ import chalk from "chalk"; import ora from "ora"; import { toSecond } from "../../lib/string-utils"; +import { fetchDatabase } from "./fetch-database"; import { fetchFaqSheets } from "./fetch-faq-sheets"; import { fetchSheets } from "./fetch-sheets"; @@ -22,6 +23,16 @@ import { fetchSheets } from "./fetch-sheets"; .then(() => { const end = `${toSecond(process.hrtime(start))} seconds`; spinner.succeed(`Fetching Sheets done in ${chalk.greenBright(end)}`); + spinner.start(`${chalk.yellowBright("Fetching next data...")}`); + }) + .catch((err) => { + chalk.red(err); + }); + + fetchDatabase() + .then(() => { + const end = `${toSecond(process.hrtime(start))} seconds`; + spinner.succeed(`Fetching Database done in ${chalk.greenBright(end)}`); }) .catch((err) => { chalk.red(err); From 374927ba12c9100e4662dce54e42f45c069a595a Mon Sep 17 00:00:00 2001 From: Hani Husamuddin Date: Wed, 1 Sep 2021 14:18:23 +0700 Subject: [PATCH 04/11] refactor: change temp --- etc/fetchers/fetch-database.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/fetchers/fetch-database.ts b/etc/fetchers/fetch-database.ts index 6185edbe5..dda0d2eb8 100644 --- a/etc/fetchers/fetch-database.ts +++ b/etc/fetchers/fetch-database.ts @@ -15,7 +15,7 @@ export async function fetchDatabase() { .map((_, li) => { const sheetId = ($(li).attr("id") as string).replace("sheet-button-", ""); const sheetName = $(li).text(); - const sheetColumns = $(`#${sheetId} tbody > tr:nth-child(1)`) + const sheetColumns = $(`#${sheetId} tbody > tr:nth-child(0)`) .find("td") .map((colIndex, td) => { colMap[colIndex] = $(td).text(); From fb3fde7a1baf37a26b95347b0c2e6e151349d53f Mon Sep 17 00:00:00 2001 From: Hani Husamuddin Date: Wed, 1 Sep 2021 14:18:52 +0700 Subject: [PATCH 05/11] refactor: undo --- etc/fetchers/fetch-database.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/fetchers/fetch-database.ts b/etc/fetchers/fetch-database.ts index dda0d2eb8..6185edbe5 100644 --- a/etc/fetchers/fetch-database.ts +++ b/etc/fetchers/fetch-database.ts @@ -15,7 +15,7 @@ export async function fetchDatabase() { .map((_, li) => { const sheetId = ($(li).attr("id") as string).replace("sheet-button-", ""); const sheetName = $(li).text(); - const sheetColumns = $(`#${sheetId} tbody > tr:nth-child(0)`) + const sheetColumns = $(`#${sheetId} tbody > tr:nth-child(1)`) .find("td") .map((colIndex, td) => { colMap[colIndex] = $(td).text(); From bd6530ff16fa71922316619eb52d65b5e5970de6 Mon Sep 17 00:00:00 2001 From: Zain Fathoni Date: Mon, 6 Sep 2021 23:09:45 +0800 Subject: [PATCH 06/11] test: decrease coverage threshold due to the complexity of mocking fetch that hinders us from testing fetch-database.ts --- jest.config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jest.config.js b/jest.config.js index 8d103ed21..0f1e5e160 100644 --- a/jest.config.js +++ b/jest.config.js @@ -7,10 +7,10 @@ module.exports = { ], coverageThreshold: { global: { - statements: 83, - branches: 74, - functions: 81, - lines: 83, + statements: 81, + branches: 75, + functions: 80, + lines: 81, }, }, transform: { From 3eb43187791d2eda4095405dcb56b20bd0d9faad Mon Sep 17 00:00:00 2001 From: Zain Fathoni Date: Tue, 7 Sep 2021 00:35:47 +0800 Subject: [PATCH 07/11] test: cover fetchDatabase() by mocking `cross-fetch` --- etc/fetchers/__mocks__/wbw-database.html | 131 ++++++++++++++++++ etc/fetchers/__tests__/fetch-database.test.ts | 25 ++++ jest.config.js | 8 +- package.json | 1 + test/jest-common.js | 1 + test/jest.server.js | 1 + test/setup.server.js | 4 + yarn.lock | 15 +- 8 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 etc/fetchers/__mocks__/wbw-database.html create mode 100644 etc/fetchers/__tests__/fetch-database.test.ts create mode 100644 test/setup.server.js diff --git a/etc/fetchers/__mocks__/wbw-database.html b/etc/fetchers/__mocks__/wbw-database.html new file mode 100644 index 000000000..8befa4bcc --- /dev/null +++ b/etc/fetchers/__mocks__/wbw-database.html @@ -0,0 +1,131 @@ + + + + + + + + [Database Utama] WargaBantuWarga - Google Drive + + + + +
+
+ [Database Utama] WargaBantuWarga +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KebutuhanKeteranganLokasiPenyediaKontakAlamatLinkTambahan InformasiTerakhir UpdateBentuk VerifikasiKetersediaan
Kontak pentingCall CenterBanda AcehAceh Tanggap Covid-19(0651) 7319111, (0651) 22118 + https://covid19.acehprov.go.id/ +
Kontak pentingKontak PentingAcehDinas Kesehatan Provinsi Aceh(0651) 22421 + Jl. Tgk. Syech Mudawali No 6 Fax 34005 + + dinkes.acehprov.go.id +
Kontak pentingKontak PentingAceh +
+ Badan Penanggulangan Bencana Aceh (BPBA) +
+
(0651) 34783 + Jalan Teungku Daud Beureueh No. 18 Kuta Alam Banda Aceh Kota 23121 +
+
+ + diff --git a/etc/fetchers/__tests__/fetch-database.test.ts b/etc/fetchers/__tests__/fetch-database.test.ts new file mode 100644 index 000000000..5c2889611 --- /dev/null +++ b/etc/fetchers/__tests__/fetch-database.test.ts @@ -0,0 +1,25 @@ +import fs from "fs"; +import path from "path"; +import fetchMock from "jest-fetch-mock"; +import { fetchDatabase } from "../fetch-database"; + +describe("fetchDatabase", () => { + beforeEach(() => { + fetchMock.resetMocks(); + }); + + it("fetches database from https://kcov.id/wbw-database correctly", async () => { + fetchMock.mockResponseOnce( + fs.readFileSync( + path.resolve(__dirname, "../__mocks__/wbw-database.html"), + "utf-8", + ), + ); + await fetchDatabase(); + + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock).toHaveBeenCalledWith("https://kcov.id/wbw-database"); + + // TODO: perform better assertions for the file contents + }); +}); diff --git a/jest.config.js b/jest.config.js index 0f1e5e160..72ab0da44 100644 --- a/jest.config.js +++ b/jest.config.js @@ -7,10 +7,10 @@ module.exports = { ], coverageThreshold: { global: { - statements: 81, - branches: 75, - functions: 80, - lines: 81, + statements: 84, + branches: 76, + functions: 82, + lines: 84, }, }, transform: { diff --git a/package.json b/package.json index a9fe1cfbc..36b40bc79 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "identity-obj-proxy": "^3.0.0", "is-ci-cli": "2.2.0", "jest": "27.0.6", + "jest-fetch-mock": "3.0.3", "jest-watch-select-projects": "2.0.0", "jest-watch-typeahead": "0.6.4", "lint-staged": "11.0.1", diff --git a/test/jest-common.js b/test/jest-common.js index 7c2767883..509350f7b 100644 --- a/test/jest-common.js +++ b/test/jest-common.js @@ -1,6 +1,7 @@ const path = require("path"); module.exports = { + automock: false, rootDir: path.join(__dirname, ".."), moduleNameMapper: { "\\.css$": "identity-obj-proxy", diff --git a/test/jest.server.js b/test/jest.server.js index 09a5ac4cb..26ade06a5 100644 --- a/test/jest.server.js +++ b/test/jest.server.js @@ -2,6 +2,7 @@ module.exports = { ...require("./jest-common"), displayName: "server", testEnvironment: "jest-environment-node", + setupFiles: ["/test/setup.server.js"], testMatch: [ "**/(etc|lib)/**/__tests__/*.test.(ts|tsx|js|jsx)", "!**/lib/__tests__/gtm.test.ts", diff --git a/test/setup.server.js b/test/setup.server.js new file mode 100644 index 000000000..c496a18e5 --- /dev/null +++ b/test/setup.server.js @@ -0,0 +1,4 @@ +import fetchMock from "jest-fetch-mock"; + +// eslint-disable-next-line no-undef +jest.setMock("cross-fetch", fetchMock); diff --git a/yarn.lock b/yarn.lock index bf45ed043..16e2fc731 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5063,7 +5063,7 @@ cross-env@^7.0.3: dependencies: cross-spawn "^7.0.1" -cross-fetch@^3.1.4: +cross-fetch@^3.0.4, cross-fetch@^3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ== @@ -8685,6 +8685,14 @@ jest-environment-node@^27.0.6: jest-mock "^27.0.6" jest-util "^27.0.6" +jest-fetch-mock@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b" + integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw== + dependencies: + cross-fetch "^3.0.4" + promise-polyfill "^8.1.3" + jest-get-type@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" @@ -11522,6 +11530,11 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +promise-polyfill@^8.1.3: + version "8.2.0" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0" + integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g== + prompts@^2.0.1, prompts@^2.2.1: version "2.4.1" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.1.tgz#befd3b1195ba052f9fd2fde8a486c4e82ee77f61" From e53f64cbbbae09b9e0b28c7f40e2c21998b9227b Mon Sep 17 00:00:00 2001 From: Zain Fathoni Date: Tue, 7 Sep 2021 00:46:40 +0800 Subject: [PATCH 08/11] test: mock fs.writeFileSync to avoid overriding the actual data while asserting the expected result through the mock file --- etc/fetchers/__mocks__/wbw-database.json | 57 +++++++++++++++++++ etc/fetchers/__tests__/fetch-database.test.ts | 20 ++++++- 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 etc/fetchers/__mocks__/wbw-database.json diff --git a/etc/fetchers/__mocks__/wbw-database.json b/etc/fetchers/__mocks__/wbw-database.json new file mode 100644 index 000000000..a0c059dd5 --- /dev/null +++ b/etc/fetchers/__mocks__/wbw-database.json @@ -0,0 +1,57 @@ +[ + { + "id": "1011184764", + "name": "Aceh", + "slug": "aceh", + "data": [ + { + "id": "0", + "kebutuhan": "Kontak penting", + "keterangan": "Call Center", + "lokasi": "Banda Aceh", + "penyedia": "Aceh Tanggap Covid-19", + "kontak": "(0651) 7319111, (0651) 22118", + "slug": "kontak-penting-call-center-banda-aceh-aceh-tanggap-covid-19-0651-7319111-0651-22118", + "alamat": "", + "link": "https://covid19.acehprov.go.id/", + "tambahan_informasi": "", + "terakhir_update": "", + "verifikasi": 0, + "bentuk_verifikasi": "", + "ketersediaan": "" + }, + { + "id": "1", + "kebutuhan": "Kontak penting", + "keterangan": "Kontak Penting", + "lokasi": "Aceh", + "penyedia": "Dinas Kesehatan Provinsi Aceh", + "kontak": "(0651) 22421", + "slug": "kontak-penting-kontak-penting-aceh-dinas-kesehatan-provinsi-aceh-0651-22421", + "alamat": "Jl. Tgk. Syech Mudawali No 6 Fax 34005", + "link": "dinkes.acehprov.go.id", + "tambahan_informasi": "", + "terakhir_update": "", + "verifikasi": 0, + "bentuk_verifikasi": "", + "ketersediaan": "" + }, + { + "id": "2", + "kebutuhan": "Kontak penting", + "keterangan": "Kontak Penting", + "lokasi": "Aceh", + "penyedia": "Badan Penanggulangan Bencana Aceh (BPBA)", + "kontak": "(0651) 34783", + "slug": "kontak-penting-kontak-penting-aceh-badan-penanggulangan-bencana-aceh-bpba-0651-34783", + "alamat": "Jalan Teungku Daud Beureueh No. 18 Kuta Alam Banda Aceh Kota 23121", + "link": "", + "tambahan_informasi": "", + "terakhir_update": "", + "verifikasi": 0, + "bentuk_verifikasi": "", + "ketersediaan": "" + } + ] + } +] diff --git a/etc/fetchers/__tests__/fetch-database.test.ts b/etc/fetchers/__tests__/fetch-database.test.ts index 5c2889611..a52a2319c 100644 --- a/etc/fetchers/__tests__/fetch-database.test.ts +++ b/etc/fetchers/__tests__/fetch-database.test.ts @@ -4,8 +4,15 @@ import fetchMock from "jest-fetch-mock"; import { fetchDatabase } from "../fetch-database"; describe("fetchDatabase", () => { + const writeFileSyncSpy = jest.spyOn(fs, "writeFileSync"); + beforeEach(() => { fetchMock.resetMocks(); + writeFileSyncSpy.mockImplementation(() => {}); + }); + + afterEach(() => { + writeFileSyncSpy.mockRestore(); }); it("fetches database from https://kcov.id/wbw-database correctly", async () => { @@ -20,6 +27,17 @@ describe("fetchDatabase", () => { expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith("https://kcov.id/wbw-database"); - // TODO: perform better assertions for the file contents + expect(writeFileSyncSpy).toHaveBeenCalledTimes(1); + expect(writeFileSyncSpy).toHaveBeenCalledWith( + path.resolve(__dirname, "../../../data/wbw-database.json"), + JSON.stringify( + JSON.parse( + fs.readFileSync( + path.resolve(__dirname, "../__mocks__/wbw-database.json"), + "utf-8", + ), + ), + ), + ); }); }); From a9c4ffe92b1f4d62a650cf5028b459b2372f9908 Mon Sep 17 00:00:00 2001 From: Zain Fathoni Date: Tue, 7 Sep 2021 00:49:44 +0800 Subject: [PATCH 09/11] fix: move fetchDatabase() call up since it's relatively faster than fetchSheets, at least for now --- etc/fetchers/fetch-wbw.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/etc/fetchers/fetch-wbw.ts b/etc/fetchers/fetch-wbw.ts index f87df969b..f39204275 100644 --- a/etc/fetchers/fetch-wbw.ts +++ b/etc/fetchers/fetch-wbw.ts @@ -19,20 +19,20 @@ import { fetchSheets } from "./fetch-sheets"; chalk.red(err); }); - fetchSheets() + fetchDatabase() .then(() => { const end = `${toSecond(process.hrtime(start))} seconds`; - spinner.succeed(`Fetching Sheets done in ${chalk.greenBright(end)}`); + spinner.succeed(`Fetching Database done in ${chalk.greenBright(end)}`); spinner.start(`${chalk.yellowBright("Fetching next data...")}`); }) .catch((err) => { chalk.red(err); }); - fetchDatabase() + fetchSheets() .then(() => { const end = `${toSecond(process.hrtime(start))} seconds`; - spinner.succeed(`Fetching Database done in ${chalk.greenBright(end)}`); + spinner.succeed(`Fetching Sheets done in ${chalk.greenBright(end)}`); }) .catch((err) => { chalk.red(err); From 677b4f16a007fdc8a526be5a63ca1f3eb7ca2ec9 Mon Sep 17 00:00:00 2001 From: Zain Fathoni Date: Tue, 7 Sep 2021 00:55:15 +0800 Subject: [PATCH 10/11] test: exclude fetch-wbw.json from the code coverage collection since fetchWbw() is an IIFE (Immediately Invoked Function Expression) which makes it harder to test while returning not so much value to the stability of the code --- jest.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/jest.config.js b/jest.config.js index 72ab0da44..c4d078f52 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,6 +4,7 @@ module.exports = { "./(components|etc|lib|pages)/**/*.(ts|tsx|js|jsx)", "!./(components|etc|lib|pages)/**/__tests__/**/*.test.(ts|tsx|js|jsx)", "!./(components|etc|lib|pages)/**/__mocks__/**/*.(ts|tsx|js|jsx)", + "!./etc/fetchers/fetch-wbw.json", ], coverageThreshold: { global: { From cad0913406483be9bbf908a24ccb4d8fcb94c289 Mon Sep 17 00:00:00 2001 From: Zain Fathoni Date: Tue, 7 Sep 2021 00:58:05 +0800 Subject: [PATCH 11/11] test: exclude fetch-wbw.ts from the code coverage collection since fetchWbw() is an IIFE (Immediately Invoked Function Expression) which makes it harder to test while returning not so much value to the stability of the code --- jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index c4d078f52..5b21ac025 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,7 +4,7 @@ module.exports = { "./(components|etc|lib|pages)/**/*.(ts|tsx|js|jsx)", "!./(components|etc|lib|pages)/**/__tests__/**/*.test.(ts|tsx|js|jsx)", "!./(components|etc|lib|pages)/**/__mocks__/**/*.(ts|tsx|js|jsx)", - "!./etc/fetchers/fetch-wbw.json", + "!./etc/fetchers/fetch-wbw.ts", ], coverageThreshold: { global: {