diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9d70fd..d72e2c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,7 @@ jobs: - name: Install dependencies if: steps.detect.outputs.found == 'true' run: make ci-setup + # Actions 會設 CI=true:make test 一律跑含容器的全部測試(見 docs/design/INIT-01.md「驗證方式」) - name: Check if: steps.detect.outputs.found == 'true' run: make check diff --git a/Makefile b/Makefile index f8327a0..90d4bdd 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,9 @@ COMPOSE := docker compose --env-file "$(ROOT)/.env" -f docker-compose.yml help: @echo "make setup install deps (Maven repo in ~/.m2, node_modules at checkout root)" @echo "make ci-setup install deps in CI (same steps as setup)" - @echo "make check lint + tests (incl. acceptance tests)" + @echo "make check lint + tests (container tests skipped when Docker is unreachable)" + @echo "backend tests incl. containers, from main checkout as one command:" + @echo " docker compose --env-file .env -f .worktrees//compose.yaml run --rm test" @echo "make infra-up start local PostgreSQL + RabbitMQ (docker compose)" @echo "make infra-down stop local services (data kept)" @echo "make api-run run backend on API_PORT" @@ -58,9 +60,18 @@ lint: else echo "skip web lint: no $(WEB_DIR)/package.json"; fi # mvnw verify 跑 src/test/java 全部(含 com.cdp.acceptance);vitest 跑 src/ 與 tests/(含 tests/acceptance) +# 連不到 Docker(本機 sandbox)或 CONTAINER_TESTS=skip 時用 -Punit-only 略過需要容器的測試並提示 compose 指令; +# CI(CI 變數非空)一律跑全部,Docker 有問題就直接失敗。見 docs/design/INIT-01.md「驗證方式」 test: @if [ -f "$(API_DIR)/pom.xml" ]; then \ - cd "$(API_DIR)" && $(NO_DOTENV) $(MVNW) verify; \ + if [ -n "$$CI" ] || { [ "$(CONTAINER_TESTS)" != skip ] && timeout 15 docker info >/dev/null 2>&1; }; then \ + cd "$(API_DIR)" && $(NO_DOTENV) $(MVNW) verify; \ + else \ + echo "Docker not reachable or CONTAINER_TESTS=skip: skipping container tests (acceptance/, *IntegrationTest)."; \ + echo "Run all backend tests from the main checkout as a single command:"; \ + echo " docker compose --env-file .env -f .worktrees//compose.yaml run --rm test"; \ + cd "$(API_DIR)" && $(NO_DOTENV) $(MVNW) -Punit-only verify; \ + fi; \ else echo "skip api tests: no $(API_DIR)/pom.xml"; fi @if [ -f "$(WEB_DIR)/package.json" ]; then \ $(NO_DOTENV) npm test -w "$(WEB_DIR)"; \ diff --git a/api/pom.xml b/api/pom.xml index 9bc987d..ba0e0a8 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -144,4 +144,27 @@ + + + + + unit-only + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*$* + com/cdp/acceptance/** + **/*IntegrationTest.java + + + + + + + diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..2982f30 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,37 @@ +# 本機跑後端全部測試(含 acceptance/)的入口;用法與取捨見 docs/design/INIT-01.md「驗證方式」。 +# 在主目錄單獨一行執行(不可包進 make/script): +# docker compose --env-file .env -f .worktrees//compose.yaml run --rm test +# 不設 name:專案名稱 = compose 檔所在目錄(worktree 名稱),平行的單各自一套。 +# 本地 PostgreSQL/RabbitMQ 仍在 docker-compose.yml(make infra-up),與此檔無關。 + +services: + test: + image: eclipse-temurin:17-jdk + # 掛 Docker socket:測試沿用 Testcontainers 自起 PostgreSQL/RabbitMQ(含 docker pause)。 + # socket 等同主機 Docker 的 root 權限,故以 root 跑、原始碼唯讀掛載、不寫回主機。 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./api:/src/api:ro + # EnvExampleNoTenantIdAcceptanceTest 從 user.dir 往上找 .env.example + - ./.env.example:/workspace/.env.example:ro + # Maven 相依與 wrapper 下載的快取;固定名稱,所有 worktree 共用 + - m2:/root/.m2 + environment: + # Testcontainers 起的容器是主機 Docker 的兄弟容器,經主機的對外 port 連 + TESTCONTAINERS_HOST_OVERRIDE: host.docker.internal + extra_hosts: + # Linux 引擎沒有內建 host.docker.internal;Docker Desktop 照常 + - "host.docker.internal:host-gateway" + working_dir: /workspace/api + # 複製原始碼(不含 target/)到容器內再建置:主機的 api/target 不受影響、不留 root 擁有的檔案 + command: + - sh + - -c + - >- + mkdir -p /workspace/api && + tar -C /src/api --exclude=./target -cf - . | tar -C /workspace/api -xf - && + sh ./mvnw -B -ntp verify -Dsurefire.useFile=false + +volumes: + m2: + name: cdp-test-m2 diff --git a/docker-compose.yml b/docker-compose.yml index 9279fe4..0db8375 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ # 本機外部服務。由 make infra-up / infra-down 操作(會帶 --env-file 主目錄 .env);變數說明見 .env.example。 # 只放 v0.1.0 需要的服務(PostgreSQL、RabbitMQ);Redis 等見 docs/design/INIT-01.md「延後」,不要先加。 -# 測試不用這套:後端整合測試由 Testcontainers 自起容器(映像版本須與此一致)。 +# 測試不用這套:後端整合測試由 Testcontainers 自起容器(映像版本須與此一致);本機跑法見 compose.yaml。 name: cdp services: diff --git a/docs/design/INIT-01.md b/docs/design/INIT-01.md index 627c266..caad5d5 100644 --- a/docs/design/INIT-01.md +++ b/docs/design/INIT-01.md @@ -14,6 +14,20 @@ | 6 | 相依只在用到的那張設計單加進 `api/pom.xml`(PostgreSQL、Flyway、AMQP 皆留給 v0.1.0 設計單,見 D-009) | 骨架不連外部服務,`make check` 不需 Docker | | 7 | spec 與本設計的已知差異,由 v0.1.0 設計單修訂 `docs/spec/`:README 第 4 行 `../plan/` 改指計劃書路徑;README「`next_wake_at` 供 db-scheduler」改依 R1;spec 05 管線入口改為「outbox → relay → RabbitMQ」 | spec 是前後端契約的上游,不可與設計矛盾 | +## 驗證方式(compose-test-setup) +| 情境 | 指令 | 跑什麼 | +|---|---|---| +| 本機全部後端測試(含 `acceptance/`) | 在主目錄單獨一行:`docker compose --env-file .env -f .worktrees//compose.yaml run --rm test`(主目錄本身用 `-f compose.yaml`) | `compose.yaml` 的 `test` 服務:`eclipse-temurin:17-jdk`,把唯讀掛載的 `api/`(不含 `target/`)複製進容器後 `mvnw verify`;不讀 `.env` 的值 | +| `make check` | 照舊 | Docker 連得到(或 CI 變數非空)跑全部;連不到或 `CONTAINER_TESTS=skip` 改 `-Punit-only`(`api/pom.xml`,排除 `com/cdp/acceptance/**`、`*IntegrationTest`),並印出上一列指令 | +| CI | `make check`(不變) | GitHub Actions 設 `CI=true`,一律全跑,Docker 壞了就紅燈 | + +| 項目 | 做法與取捨 | +|---|---| +| Testcontainers | 沿用(不改測試、`docker pause` 照常):`test` 掛 `/var/run/docker.sock`,PG/RabbitMQ 是主機引擎上的兄弟容器,經 `TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal`(Linux 由 `extra_hosts: host-gateway` 補)連對外 port;Ryuk 照常清理 | +| 安全 | socket 等同主機 Docker root 權限,故只掛 `api/`、`.env.example`(皆唯讀)與 Maven 快取 volume,不寫回主機(所以以 root 跑、不需 `HOST_UID`);Testcontainers 對外 port 與在主機直接跑時相同 | +| 隔離與殘留 | 不設 `name:`,專案名 = worktree 目錄;`run --rm` 刪 `test` 容器,Testcontainers 容器由 Ryuk 收;留下的只有專案網路(`docker compose -f … down` 可刪)與共用快取 volume `cdp-test-m2` | +| 新測試慣例 | 需要容器的測試只放 `acceptance/` 或命名 `*IntegrationTest`,否則 sandbox 的 `make check` 會失敗 | + ## RabbitMQ(D-006;守計劃書 7.2.1 三條紅線) | 項目 | 設計 | |---|---|