Closed yh-project closed 6 days ago
requested review from @ABizCho
@altys31 @ABizCho
컀λ°μ 지λΌμ mr μ°λ λ°©λ²μ λͺ°λΌμ νλ²μ μ¬λ Έμλ€. μ΅λν λ¨μνκ² μ μ΄λ΄€λλ° λ΄μ©μ΄ κ·Έλλ μ« λ§μμ¬ γ γ ... 빨리 ν΄μΌνλκ±° μλλκΉ μ²μ²ν μ½μ΄λ³΄μκ³ λ΄μΌ μ€μ κΉμ§λ§ 리뷰ν΄μ£Όμμ ©.
In GitLab by @ABizCho on Nov 11, 2024, 09:43
approved this merge request
In GitLab by @ABizCho on Nov 11, 2024, 09:47
Commented on shooot-mock/src/index.d.ts line 38
Axios νμ μ μλ₯Ό 보λκ²λ μ μ νκ³ , μ΄μ°Έμ λ©λ¦¬νλ νμ μ€ν¬λ¦½νΈ μ λ€λ¦ λ¬Έμλ₯Ό μ°Ύμλ΄μ λμμ΄ λμ΅λλ€.
In GitLab by @ABizCho on Nov 11, 2024, 09:47
Commented on shooot-mock/src/index.js line 31
ν¨μν μ²λ¦¬λ‘ pathVariable μ²λ¦¬λ°©μμ΄ κ°κ²°ν΄μ μ’μμ
In GitLab by @ABizCho on Nov 11, 2024, 09:47
κ΅μ₯ν λμ μ μΈ ννΈμλλ°, μ½λλ κΉλνκ³ λͺ¨νΉμ΄λΌλ ν΅μ¬ κΈ°λ₯κ³Ό μ ν¬ μλΉμ€μμ μ μ μ μ΅μ§μ€λ½μ§ μμ μ’μ λ°©λ²μΌλ‘ λ무 μ μ°κ²°νμ κ² κ°μμ. λ¬Έμλ μ΄ν΄κ° μ κ°λ€μ κ³ μ λ§μΌμ ¨μ΄μ!
In GitLab by @altys31 on Nov 11, 2024, 10:29
κ° μΉμ
λ³λ‘ μ μ 리ν΄μ£Όμ
μ μ½κ² μ΄ν΄ν μ μμμ΅λλ€. πππ
볡μ‘ν μ€μ μμ΄ npx shooot initλ§μΌλ‘ μ¬μ© κ°λ₯ν΄μ μ΄κΈ° μ€μ λΆλ΄μ΄ μμ΄μ μ’μκ² κ°μ΅λλ€.
μκ³ λ§μΌμ
¨μ΄μ πΈ
Merges feat/40-request-mocking -> mock
π₯₯ Contents
service worker ꡬν
μ¬μ©μμ http μμ²μ λ°μμ mocking μ²λ¦¬λ₯Ό νκΈ° μν service worker μ κΈ°λ₯μ ꡬνν΄μΌ νλ€. fetch μ΄λ²€νΈλ₯Ό λ±λ‘νμ¬ λΈλΌμ°μ μμ λμνκ³ μλ service worker κ° μμ²μ μΈμνμ¬ μ²λ¦¬λ₯Ό νκ² λλ€.
μ¬μ©μ μμ² κ°λ‘μ±κΈ°
```javascript // shooot-mock/service-worker.js if (projectName === undefined || !projectName.length) { console.error("[Fetch Event]: projectName μ λ±λ‘ν΄μ£ΌμΈμ"); } else { const { request } = event; const url = new URL(request.url); if (!url.origin.split(/[/.]/).includes(projectName)) { console.error("[Fetch Event]: μλΉμ€μμ μ 곡λλ λλ©μΈμΌλ‘μ μμ²λ§ mocking λ©λλ€"); } else { ... } } ``` μμ²μ λ°μΌλ©΄ service worker κ° ν μΌμ μμ²μ κ΄λ¦¬ν΄λ λλμ§μ λν μ¬λΆ νμ μ΄λ€. - projectName(μ°λ¦¬ μλΉμ€μ λ±λ‘ν μλΉμ€μ μλ¬Έλͺ )μ μ€μ νμ§ μμ κ²½μ° - μ€μ ν projectName μ΄ λλ©μΈμ ν¬ν¨λ μμ²μ κ²½μ°λ°μ΄ν° κ°κ³΅νκΈ°
```javascript // shooot-mock/service-worker.js event.respondWith( (async () => { const origin = url.origin; // origin === domain const method = request.method; // request method const headers = [...request.headers.entries()]; // request header const params = Object.fromEntries(url.searchParams.entries()); // request parameters, path variables const requestParameters = {}; const pathVariables = {}; Object.keys(params).forEach((key) => { // request parameter, path variable κ°κ³΅ κ³Όμ }); let body = {}; if (["POST", "PUT", "PATCH"].includes(method)) { // request body κ°κ³΅ κ³Όμ } const postData = { // express μ μ λ¬ν λ°μ΄ν°λ‘ λ³ν }; let result; try { const response = await fetch("express endpoint", { // post λ‘ λ°μ΄ν° μ λ¬ }); console.log(`[Fetch Event]: from express ${result}`); return new Response(JSON.stringify(await response.json()), { status: 200 }); } catch (error) { console.error(`[Fetch Event]: from express error`, error); return new Response(JSON.stringify({ error: "[Fetch Event]: server internal error" }), { status: 500, }); } })() ); ``` - νμ¬ μ¬μ©μμ request μμ μ¬μ©νλ μ 보λ μμμ 보μ¬μ§λ 6κ°μ§(origin, method, headers, request parameters, path variables, body) - λ½μλΈ λ°μ΄ν°λ₯Ό νλμ κ°μ²΄(postData)λ‘ κ°μΈμ express μλ²λ‘ μ λ¬ - express μ μλ΅μ μ¬μ©μμκ² μ λ¬message μ΄λ²€νΈ
```javascript // shooot-mock/service-worker.js self.addEventListener("message", async function (event) { console.log("[νλ‘μ νΈ μ€μ ]=========="); if (event.data && event.data.type === "SET_CONFIGS") { projectName = event.data.projectName; delay = event.data.delay; console.log(`[νλ‘μ νΈ μ€μ ]: μ 보 μ€μ μλ£ (${projectName}, ${delay}ms)`); } }); ``` - μμμ μΈκΈλ projectName μ service worker κ° μλ λΌμ΄λΈλ¬λ¦¬κ° μ 곡νλ object μ λ©μλκ° λ°μμ μμμΌλ‘ μ μ₯ - object κ° κ°μ μ λ¬ν΄μ€μΌ νκ³ , κ·Έ κ³Όμ μ service worker κ° λ³΄μ νκ³ μλ postMessage λ©μλμ message μ΄λ²€νΈλ‘ κ΄λ¦¬ - λΌμ΄λΈλ¬λ¦¬μμμ ꡬν μ½λλ λ€μ merge requeset μμ νμΈμ΄ κ°λ₯νκ³ μ¬κΈ°μλ message μ΄λ²€νΈμμ λ°μ΄ν°λ₯Ό μ²λ¦¬νλ κ³Όμ λ§ μκ°λΌμ΄λΈλ¬λ¦¬ ꡬν
μ°λ¦¬μ μ΅μ’ λͺ©νλ service worker κ° λμνλ λΌμ΄λΈλ¬λ¦¬λ₯Ό ꡬννμ¬ κ°λ°μλ€μ΄ μΈ μ μλλ‘ νλ κ²μ΄λ€.
λΌμ΄λΈλ¬λ¦¬μμ μ 곡νλ object - κ΄λ¦¬ μμ
```javascript // shooot-mock/index.js shooot.projectName = ""; shooot.delay = 0; shooot.axios = {}; ``` - 3κ°μ μμμ κ΄λ¦¬ - νλ‘μ νΈ λλ©μΈ νλ³μ μν projectName, μμ² delay μκ° - path variables λ₯Ό μ§μ κ°μ²΄ ννλ‘ μ μνκΈ° μν΄ κ°κ³΅ν axios ```javascript // shooot-mock/index.js /** * Custom GET request * ν νλ¦Ώ, 맀κ°λ³μ, λ°νκ°μ λν μ μ */ shooot.axios.get = async function (url, config = {}, pathVariables = {}) { let to = url; if (pathVariables) { // κ°μ²΄ ννλ‘ λ°μ path variable μ url μ ν¬ν¨μν€λ κ³Όμ Object.keys(pathVariables).forEach((k) => (to += `/${pathVariables[k]}`)); } const swState = await checkServiceWorker("Axios-Get"); // service worker κ° νλνκ³ μλκ° const domainState = checkDomain(to, shooot.projectName); // μ¬μ©ν μ μλ projectName μΈκ° const newConfig = swState && domainState ? getAxiosConfigs(pathVariables, config) : { ...config, params: { ...config.params } }; // λλ€ true λΌλ©΄ mocking μ μν config λ‘, μλλΌλ©΄ κΈ°λ³Έ axios μμ²μ μν config λ‘ return axios.get(to, newConfig); // κ°κ³΅ν μ λ³΄λ‘ axios μμ² }; ``` - κΈ°μ‘΄μ axios μμλ path variables λ₯Ό κ°μ²΄ ννλ‘ μ μΈνμ§ λͺ»ν¨ - κ°μ²΄ ννλ‘ μ μΈν μ μλλ‘ κ°κ³΅ -> mocking ν λ μμμ κ°κ³΅ν μ μλλ‘ νκΈ° μν¨ - mocking ν΄μΌ νλ μμ²μΈμ§ νλ³νμ¬ μν©μ λ§λ config λ‘ κ°κ³΅ - κΈ°μ‘΄μ axios λ₯Ό νΈμΆνμ¬ μμ²μ 보λ΄μ΄ mocking μ΄λΌλ©΄ service worker κ°, μλλΌλ©΄ κΈ°μ‘΄ μμ²μ΄ κ·Έλλ‘ μ§ν - μ΄μ κ°μ λ°©μμΌλ‘ get, post, put, patch, delete ꡬνλΌμ΄λΈλ¬λ¦¬μμ μ 곡νλ object - λ©μλ
```javascript // shooot-mock/index.js shooot.register = async function () { console.log("[Register]=========="); if ("serviceWorker" in navigator) { try { await navigator.serviceWorker.register("/service-worker.js"); console.log(`[Register]: service worker λ±λ‘ μλ£`); } ... } ... }; shooot.unregister = async function () { console.log("[UnRegister]=========="); if ("serviceWorker" in navigator) { try { const registration = await navigator.serviceWorker.getRegistration(); if (registration) { await registration.unregister(); console.log("[UnRegister]: service worker λ±λ‘ ν΄μ νμμ΅λλ€"); } ... } ... } ... }; shooot.controller = async function (mode) { if (process.env.NODE_ENV === (mode ? mode : "development")) { await this.register(); } else { await this.unregister(); } }; ``` - λΈλΌμ°μ μ service worker λ₯Ό λ±λ‘νλ register - λΈλΌμ°μ μμ service worker λ₯Ό λ±λ‘ ν΄μ νλ unregister - 맀κ°λ³μλ‘ λ°λ mode κ° μλ€λ©΄ mode or development λͺ¨λμΌ λ register, μλ λμλ unregister ```javascript // shooot-mock/index.js shooot.setConfigs = async function (projectName, delay) { const projectNameRegex = /^[a-z0-9-]{1,20}$/; console.log("[Set-Config]=========="); if (!projectName) { // projectName μ΄ μ λ ₯λμλκ° } else if (!projectName.trim().length || !projectNameRegex.test(projectName.trim())) { // μ¬μ© κ°λ₯ν projectName μΈκ°} else { // λ±λ‘λ νλ‘μ νΈμΈμ§ νμΈ const pn = projectName.trim(); const canUse = await checkProjectName(pn); // λ±λ‘λ νλ‘μ νΈ if (canUse === "AVAILABLE") { const checkServiceWorkerResult = await checkServiceWorker("Set-Config"); if (checkServiceWorkerResult) { shooot.projectName = projectName; shooot.delay = delay; const message = { type: "SET_CONFIGS", projectName: shooot.projectName, delay: shooot.delay, }; checkServiceWorkerResult.active.postMessage(message); console.log(`[Set-Config]: message λ₯Ό μ λ¬νμ΅λλ€`); } } else if (canUse === "UNAVAILABLE") { ... // λ±λ‘λμ§ μμ νλ‘μ νΈ } } }; ``` - μ¬μ©κ°λ₯ν projectName μΈμ§ μ°μ νλ³ - service worker κ° λμ μ€μΈμ§ νμΈ - service worker μκ² μ€μ κ° μ λ¬λΌμ΄λΈλ¬λ¦¬μμ μ 곡νλ util λ©μλ, type
```javascript // shooot-mock/utils.js export const checkProjectName = async (projectName) => { ... // express μμ λ±λ‘λ projectName μΈμ§ νμΈ }; export const checkServiceWorker = async (keyword) => { ... // service worker κ° λμ μ€μ΄λ©΄ true λ₯Ό, μλλ©΄ false λ₯Ό }; export const checkDomain = (to, domain) => { ... // μμ²μ url μμ domain(=projectName) μ΄ ν¬ν¨λμ΄μλμ§ νμΈ }; export const getAxiosConfigs = (pathVariables, config) => { ... // mocking μ μν axios config λ‘ κ°κ³΅ }; ``` ```javascript // shooot-mock/index.d.ts export interface Configs { // μ€μ κ° projectName: string; delay?: number; } export interface Shooot extends Configs { axios: OmitλΌμ΄λΈλ¬λ¦¬ μ¬μ©
init script
```bash npx shooot initexpress μλ² κ΅¬ν
λ±λ‘λ projectName μΈκ°
```javascript // shooot-express/server.js app.get("/projects/search", (req, res) => { const { projectName } = req.query; const query = `SELECT * FROM project WHERE english_name = "${projectName}"`; db.execute(query, (err, results) => { ... // κ²°κ³Ό λ°ν }); }); ``` - express μλ²μμ μ°λ¦¬ μλΉμ€μ λ±λ‘λ projectName μΈμ§ νμΈνμ¬ κ²°κ³Ό λ°νservice worker λ‘λΆν° λ°μ κ°κ³΅λ λ°μ΄ν°
```javascript // shooot-express/server.js app.post("/mock/data", (req, res) => { const { projectName, url, origin, method, headers, requestParameters, pathVariables, body } = req.body; console.log("[Received data from Service Worker]"); ... // λ¨μν μ½μμ λ‘κ·Έ λ¨κΈ°κΈ° res.json({ message: "λ°μ΄ν°λ₯Ό μ μμ μΌλ‘ λ°μμ΅λλ€" }); }); ``` - service worker λ‘λΆν° μ λ¬ λ°μ κ°κ³΅λ λ°μ΄ν°λ₯Ό νμ¬λ λ¨μν μ½μμ λ‘κ·Έλ§ λ¨κΉ - νμ api docs μͺ½ κΈ°λ₯μ΄ μμ±λλ©΄ λ¨μ κΈ°λ₯ ꡬν μμ πΈ Screenshot
β Related Issue
40
π Reference
chatGPT κ³ λ§μμ©