@@ -244,6 +244,27 @@ GitHub Organization webhook ์์ ์ฉ ์๋ํฌ์ธํธ
2442444 . Week ์์ โ ๊ฒฝ๊ณ ๋๊ธ ์์ฑ (์ค๋ณต ๋ฐฉ์ง: Bot์ด ์์ฑํ ๊ฒฝ๊ณ ๋๊ธ์ด ์ด๋ฏธ ์์ผ๋ฉด ์คํต)
2452455 . Week ์์ โ ๊ธฐ์กด ๊ฒฝ๊ณ ๋๊ธ ์ญ์ (Bot์ด ์์ฑํ Week ๊ฒฝ๊ณ ๋๊ธ๋ง)
246246
247+ ### 4. AI ํธ๋ค๋ฌ Worker ๋ถ๋ฆฌ ์ํคํ
์ฒ
248+
249+ PR ์ด๋ฒคํธ๋ฅผ ๋ฐ์ผ๋ฉด webhook ํธ๋ค๋ฌ๊ฐ ๋ AI ํธ๋ค๋ฌ(` tagPatterns ` , ` postLearningStatus ` )๋ฅผ ** ๋ณ๋ Worker invocation** ์ผ๋ก ๋ถ๋ฆฌ ๋์คํจ์นํ๋ค. ๊ฐ invocation์ ๋
๋ฆฝ์ ์ธ Cloudflare subrequest ์์ฐ(50)์ ๊ฐ์ง๋ฏ๋ก ํ์ผ์ด ๋ง์ PR์์๋ ์์ฐ ์ด๊ณผ๋ฅผ ๋ฐฉ์งํ๋ค.
250+
251+ ```
252+ GitHub webhook
253+ โ
254+ โผ
255+ [Invocation #1] webhook ํธ๋ค๋ฌ โ 50 subrequest ์์ฐ
256+ โ ctx.waitUntil(fetch("/internal/tag-patterns")) โโ self-fetch๋
257+ โ ctx.waitUntil(fetch("/internal/learning-status")) โโค ์ธ๋ถ ์์ฒญ์ด๋ผ
258+ โ โ ์ invocation ํธ๋ฆฌ๊ฑฐ
259+ โโโโโโโโโโโโโโโโถ [Invocation #2] tagPatterns โโโ โ ๋
๋ฆฝ 50 ์์ฐ
260+ โ
261+ โโโโโโโโโโโโโโโโถ [Invocation #3] postLearningStatus โ ๋
๋ฆฝ 50 ์์ฐ
262+ ```
263+
264+ - ` INTERNAL_SECRET ` ๊ณผ ` WORKER_URL ` ์ด ๋ชจ๋ ์ค์ ๋์ด์ผ ํ์ฑํ๋๋ค. ๋ ์ค ํ๋๋ผ๋ ์์ผ๋ฉด ๊ธฐ์กด์ฒ๋ผ ๊ฐ์ invocation์์ ์์ฐจ ์คํ(subrequest ์์ฐ ๊ณต์ )๋์ด ํ์ผ์ด ๋ง์ PR์์ ์์ฐ์ ์ด๊ณผํ ์ ์๋ค.
265+ - ๋ด๋ถ ์๋ํฌ์ธํธ๋ ` /internal/tag-patterns ` , ` /internal/learning-status ` ์ด๋ฉฐ ` X-Internal-Secret ` ํค๋๋ก ์ธ์ฆํ๋ค.
266+ - ์ฐธ๊ณ : ` tests/subrequest-budget.test.js ` ๊ฐ 5๊ฐ ํ์ผ ๋ณ๊ฒฝ ์๋๋ฆฌ์ค์์ ๊ฐ ํธ๋ค๋ฌ์ fetch ํธ์ถ ์(๊ฐ๊ฐ 22, 15ํ)๋ฅผ ํ๊ท ํ
์คํธ๋ก ๋ฐ์๋๋ค.
267+
247268## ๋ณด์ ๋ฐ ๊ถํ
248269
249270### DaleStudy Organization ์ ์ฉ
@@ -328,8 +349,16 @@ wrangler secret put OPENAI_API_KEY
328349
329350# Webhook Secret (์ ํ์ฌํญ)
330351wrangler secret put WEBHOOK_SECRET
352+
353+ # Internal Dispatch Secret (AI ํธ๋ค๋ฌ Worker ๋ถ๋ฆฌ์ฉ, ๊ถ์ฅ)
354+ # ์ค์ ํ๋ฉด tagPatterns, learningStatus๊ฐ ๋ณ๋ Worker ํธ์ถ๋ก
355+ # ๋์คํจ์น๋์ด ๊ฐ๊ฐ ๋
๋ฆฝ์ ์ธ subrequest ์์ฐ์ ๊ฐ์ง.
356+ # WORKER_URL๊ณผ ํจ๊ป ์ค์ ๋์ด์ผ ํ์ฑํ๋๋ค.
357+ wrangler secret put INTERNAL_SECRET
331358```
332359
360+ ` WORKER_URL ` ์ ` wrangler.jsonc ` ์ ` vars ` ์ ์ ์๋์ด ์์ด ๊ธฐ๋ณธ ๋ฐฐํฌ์๋ ์ถ๊ฐ ์ค์ ์ด ํ์ ์๋ค. ์คํ
์ด์ง/๋ค๋ฅธ ๊ณ์ ๋ฑ์ผ๋ก ๋ฐฐํฌํ ๋๋ง ๋ฎ์ด์ฐ๋ฉด ๋๋ค.
361+
333362### 5. GitHub App ์ค์น
334363
335364์ ์ฅ์์ App์ด ์ค์น๋์ด ์๋์ง ํ์ธ:
@@ -359,15 +388,79 @@ curl -X POST https://github.dalestudy.com/check-weeks \
359388- โ npm ํจํค์ง ๋๋ถ๋ถ ํธํ ์ ๋จ (@octokit/app ๋ฑ)
360389- โ
์์ JavaScript + Web APIs๋ก ๊ตฌํ
361390
391+ ## ํ
์คํธ
392+
393+ ์ด ํ๋ก์ ํธ๋ [ Bun] ( https://bun.sh ) ์ ๋ด์ฅ ํ
์คํธ ๋ฌ๋๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๋ณ๋์ ` package.json ` ์ด๋ ์์กด์ฑ ์ค์น ์์ด ํ
์คํธ๋ฅผ ์์ฑํ๊ณ ์คํํ ์ ์์ต๋๋ค.
394+
395+ ### ํ
์คํธ ์คํ
396+
397+ ํ
์คํธ๋ ` handlers/ ` (ํธ๋ค๋ฌ๋ณ ๋จ์ ํ
์คํธ)์ ` tests/ ` (ํ๋ก์ธ์ค ๊ฒฉ๋ฆฌ๊ฐ ํ์ํ ํ
์คํธ)๋ก ๋๋์ด ์๋ค. Bun์ ` vi.mock() ` ์ ํ๋ก์ธ์ค ์ ์ญ ๋ ์ง์คํธ๋ฆฌ์ ๋ฑ๋ก๋์ด ๊ฐ์ ์คํ ๋ด์์ ๋ค๋ฅธ ํ์ผ๋ก ๋์ถ๋๋ฏ๋ก, ๊ฐ์ ๋ชจ๋์ ๋ชจํนํ๋ ํ
์คํธ์ ์ค์ ๊ตฌํ์ ํธ์ถํ๋ ํ
์คํธ๋ ** ๋ณ๋ ` bun test ` ํ๋ก์ธ์ค๋ก ์คํ** ํด์ผ ํ๋ค.
398+
399+ ``` bash
400+ # ์ ์ฒด ํ
์คํธ ์คํ (๋ ๋๋ ํ ๋ฆฌ๋ฅผ ๋ณ๋ ํ๋ก์ธ์ค๋ก)
401+ bun test handlers/ && bun test tests/
402+
403+ # ํน์ ํ์ผ๋ง ์คํ
404+ bun test handlers/webhooks.test.js
405+
406+ # ๊ฐ์ ๋ชจ๋ (ํ์ผ ๋ณ๊ฒฝ ์ ์๋ ์ฌ์คํ)
407+ bun test handlers/ --watch
408+ ```
409+
410+ Bun ์ค์น: https://bun.sh/docs/installation
411+
412+ ### ํ
์คํธ ํ์ผ ์์ฑ ๊ท์น
413+
414+ - ํ
์คํธ ํ์ผ์ ๋์ ํ์ผ๊ณผ ๊ฐ์ ๋๋ ํ ๋ฆฌ์ ` *.test.js ` ์ด๋ฆ์ผ๋ก ๋ฐฐ์นํฉ๋๋ค.
415+ - ์: ` handlers/webhooks.js ` โ ` handlers/webhooks.test.js `
416+ - ` bun:test ` ์์ ์ ๊ณตํ๋ API(` describe ` , ` it ` , ` expect ` , ` vi ` )๋ฅผ ์ฌ์ฉํฉ๋๋ค.
417+ - ์ธ๋ถ ์์กด์ฑ(` utils/github.js ` ๋ฑ)์ ` vi.mock() ` ์ผ๋ก ๋์ฒดํ๊ณ , ` fetch ` ๋ ` globalThis.fetch = vi.fn()... ` ๋ก ์คํ
ํฉ๋๋ค.
418+
419+ ``` javascript
420+ import { describe , it , expect , vi , beforeEach } from " bun:test" ;
421+
422+ vi .mock (" ../utils/github.js" , () => ({
423+ generateGitHubAppToken: vi .fn ().mockResolvedValue (" fake-token" ),
424+ }));
425+
426+ import { checkWeeks } from " ./check-weeks.js" ;
427+
428+ describe (" checkWeeks" , () => {
429+ beforeEach (() => {
430+ vi .clearAllMocks ();
431+ globalThis .fetch = vi .fn ().mockResolvedValue ({
432+ ok: true ,
433+ json : () => Promise .resolve ([]),
434+ });
435+ });
436+
437+ it (" returns 403 for non-DaleStudy organization" , async () => {
438+ const request = new Request (" https://example.com/check-weeks" , {
439+ method: " POST" ,
440+ headers: { " Content-Type" : " application/json" },
441+ body: JSON .stringify ({ repo_owner: " OtherOrg" , repo_name: " leetcode-study" }),
442+ });
443+
444+ const response = await checkWeeks (request, {});
445+ expect (response .status ).toBe (403 );
446+ });
447+ });
448+ ```
449+
450+ ### CI ์๋ ์คํ
451+
452+ ` .github/workflows/integration.yaml ` ์ด ๋ชจ๋ Pull Request์ ` main ` ๋ธ๋์น ํธ์์์ ` bun test handlers/ ` ์ ` bun test tests/ ` ๋ฅผ ๊ฐ๊ฐ ๋ณ๋ ์คํ
์ผ๋ก ์๋ ์คํํฉ๋๋ค. ํ
์คํธ๊ฐ ์คํจํ๋ฉด PR ์ฒดํฌ๊ฐ ์คํจํ๋ฏ๋ก, ๋จธ์ง ์ ์ ๋ฐ๋์ ํต๊ณผ์์ผ์ผ ํฉ๋๋ค.
453+
362454## ์ ๊ธฐ๋ฅ ์ถ๊ฐ ๊ฐ์ด๋
363455
364456์๋ก์ด ์๋ํ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ๋ ๋ค์ ๋จ๊ณ๋ฅผ ๋ฐ๋ฅด์ธ์:
365457
3664581 . ** ์๋ํฌ์ธํธ ์ถ๊ฐ** : ` index.js ` ์ ` fetch() ` ํจ์์ ์๋ก์ด pathname ๋ผ์ฐํ
์ถ๊ฐ
3674592 . ** ํธ๋ค๋ฌ ํจ์ ์์ฑ** : ๋น์ฆ๋์ค ๋ก์ง์ ๋ณ๋ ํจ์๋ก ๋ถ๋ฆฌ (์: ` handleCheckAllPrs ` )
3684603 . ** GitHub App ๊ถํ ํ์ธ** : ํ์ํ ๊ถํ์ด ์๋์ง ํ์ธํ๊ณ ์์ผ๋ฉด ์ถ๊ฐ
369- 4 . ** ๋ฌธ์ ์
๋ฐ์ดํธ** : AGENTS.md, README.md์ ์ ๊ธฐ๋ฅ ๋ฌธ์ํ
370- 5 . ** ํ
์คํธ** : ๋ก์ปฌ(` wrangler dev ` )์์ ๋จผ์ ํ
์คํธ ํ ๋ฐฐํฌ
461+ 4 . ** ํ
์คํธ ์์ฑ** : ํธ๋ค๋ฌ ์์ ` *.test.js ` ๋ฅผ ์ถ๊ฐํ๊ณ ` bun test ` ๋ก ํต๊ณผ ํ์ธ (์ "ํ
์คํธ" ์น์
์ฐธ๊ณ )
462+ 5 . ** ๋ฌธ์ ์
๋ฐ์ดํธ** : AGENTS.md, README.md์ ์ ๊ธฐ๋ฅ ๋ฌธ์ํ
463+ 6 . ** ๋ก์ปฌ ์คํ ํ
์คํธ** : ` wrangler dev ` ๋ก ์ค์ ์๋ํฌ์ธํธ ๋์ ํ์ธ ํ ๋ฐฐํฌ
371464
372465## ์ฝ๋ ์์ ์ ์ฃผ์์ฌํญ
373466
0 commit comments