Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 430e61fdbd | |||
| b8aa63e7ba | |||
| d855064765 | |||
| 38bb01b4b4 | |||
| c96ebcc6e0 | |||
| 34ff4c5c17 |
@@ -141,16 +141,6 @@ inputs:
|
|||||||
description: 'Maximum bytes of injected doc content from doc-map (default 102400 = 100KB)'
|
description: 'Maximum bytes of injected doc content from doc-map (default 102400 = 100KB)'
|
||||||
required: false
|
required: false
|
||||||
default: '102400'
|
default: '102400'
|
||||||
doc-map-trusted-ref:
|
|
||||||
description: >-
|
|
||||||
Git ref (branch, tag, or SHA) from which to fetch the doc-map config file
|
|
||||||
via VCS API instead of reading it from the local workspace. Recommended
|
|
||||||
when using doc-map: set this to the default branch (e.g. 'main') so a
|
|
||||||
malicious PR cannot modify the doc-map config to inject arbitrary design
|
|
||||||
docs into the LLM prompt. When unset, the config is read from the local
|
|
||||||
workspace (the PR branch) with a security warning in the logs.
|
|
||||||
required: false
|
|
||||||
default: ''
|
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: 'composite'
|
using: 'composite'
|
||||||
@@ -517,7 +507,6 @@ runs:
|
|||||||
PERSONA_FILE: ${{ inputs.persona-file }}
|
PERSONA_FILE: ${{ inputs.persona-file }}
|
||||||
DOC_MAP_FILE: ${{ inputs.doc-map }}
|
DOC_MAP_FILE: ${{ inputs.doc-map }}
|
||||||
DOC_MAP_MAX_BYTES: ${{ inputs.doc-map-max-bytes }}
|
DOC_MAP_MAX_BYTES: ${{ inputs.doc-map-max-bytes }}
|
||||||
DOC_MAP_TRUSTED_REF: ${{ inputs.doc-map-trusted-ref }}
|
|
||||||
AICORE_CLIENT_ID: ${{ inputs.aicore-client-id }}
|
AICORE_CLIENT_ID: ${{ inputs.aicore-client-id }}
|
||||||
AICORE_CLIENT_SECRET: ${{ inputs.aicore-client-secret }}
|
AICORE_CLIENT_SECRET: ${{ inputs.aicore-client-secret }}
|
||||||
AICORE_AUTH_URL: ${{ inputs.aicore-auth-url }}
|
AICORE_AUTH_URL: ${{ inputs.aicore-auth-url }}
|
||||||
|
|||||||
@@ -6,19 +6,12 @@
|
|||||||
|
|
||||||
- **`validateDocmapPath`: add `EvalSymlinks` to close directory-symlink bypass** ([#150](https://gitea.weiker.me/rodin/review-bot/issues/150)): The previous implementation used `os.Lstat` which only avoids following the *final* path component. An intermediate directory symlink (e.g. `.review-bot/` committed as a symlink to a directory outside the repo) would pass the path-confinement check because the textual path appeared within the repo root. `filepath.EvalSymlinks` is now called first, resolving all symlink components before the `filepath.Rel` confinement check. In-repo symlinks whose resolved targets also reside within the repo root are now allowed; out-of-repo targets are rejected by the confinement check.
|
- **`validateDocmapPath`: add `EvalSymlinks` to close directory-symlink bypass** ([#150](https://gitea.weiker.me/rodin/review-bot/issues/150)): The previous implementation used `os.Lstat` which only avoids following the *final* path component. An intermediate directory symlink (e.g. `.review-bot/` committed as a symlink to a directory outside the repo) would pass the path-confinement check because the textual path appeared within the repo root. `filepath.EvalSymlinks` is now called first, resolving all symlink components before the `filepath.Rel` confinement check. In-repo symlinks whose resolved targets also reside within the repo root are now allowed; out-of-repo targets are rejected by the confinement check.
|
||||||
|
|
||||||
- **`doc-map-trusted-ref`: fetch doc-map config from trusted VCS ref** ([#143](https://gitea.weiker.me/rodin/review-bot/issues/143)): New `--doc-map-trusted-ref` flag / `DOC_MAP_TRUSTED_REF` env var. When set, the doc-map YAML config is fetched from the specified VCS ref (e.g. `main`) via API instead of being read from the local workspace (the PR branch checkout). This prevents a malicious PR from modifying `.review-bot/doc-map.yml` to inject arbitrary design docs into the LLM prompt. When unset, the local workspace is used with a security warning in the logs.
|
|
||||||
|
|
||||||
### Tests
|
### Tests
|
||||||
|
|
||||||
- **`TestValidateDocmapPath_DirSymlinkBypass`**: verifies that a directory symlink inside the repo pointing outside cannot be used to bypass path confinement ([#150](https://gitea.weiker.me/rodin/review-bot/issues/150)).
|
- **`TestValidateDocmapPath_DirSymlinkBypass`**: verifies that a directory symlink inside the repo pointing outside cannot be used to bypass path confinement ([#150](https://gitea.weiker.me/rodin/review-bot/issues/150)).
|
||||||
|
|
||||||
- **`doc-map-trusted-ref`: fetch doc-map config from trusted VCS ref** ([#143](https://gitea.weiker.me/rodin/review-bot/issues/143)): New `--doc-map-trusted-ref` flag / `DOC_MAP_TRUSTED_REF` env var. When set, the doc-map YAML config is fetched from the specified VCS ref (e.g. `main`) via API instead of being read from the local workspace (the PR branch checkout). This prevents a malicious PR from modifying `.review-bot/doc-map.yml` to inject arbitrary design docs into the LLM prompt. When unset, the local workspace is used with a security warning in the logs.
|
|
||||||
>>>>>>> 3222c76 (feat(#143): fetch doc-map config from trusted VCS ref)
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **`doc-map-trusted-ref` input** (`--doc-map-trusted-ref` flag / `DOC_MAP_TRUSTED_REF` env var): Git ref (branch, tag, or SHA) from which to fetch the doc-map config via VCS API. Recommended for all `doc-map` users. Example: `doc-map-trusted-ref: main`. ([#143](https://gitea.weiker.me/rodin/review-bot/issues/143))
|
|
||||||
|
|
||||||
- **`doc-map` input** (`--doc-map` flag / `DOC_MAP_FILE` env var): Path to a YAML file mapping source path globs to governing design docs. review-bot intersects the map with changed PR paths and injects matching docs into the system prompt under a `## Design Documents` heading. ([#137](https://gitea.weiker.me/rodin/review-bot/issues/137))
|
- **`doc-map` input** (`--doc-map` flag / `DOC_MAP_FILE` env var): Path to a YAML file mapping source path globs to governing design docs. review-bot intersects the map with changed PR paths and injects matching docs into the system prompt under a `## Design Documents` heading. ([#137](https://gitea.weiker.me/rodin/review-bot/issues/137))
|
||||||
- **`doc-map-max-bytes` input** (`--doc-map-max-bytes` flag / `DOC_MAP_MAX_BYTES` env var): Cap on total injected design doc content in bytes. Default: 102400 (100 KB). Prevents accidental context overflow when a PR touches many modules.
|
- **`doc-map-max-bytes` input** (`--doc-map-max-bytes` flag / `DOC_MAP_MAX_BYTES` env var): Cap on total injected design doc content in bytes. Default: 102400 (100 KB). Prevents accidental context overflow when a PR touches many modules.
|
||||||
- **`DesignDocs` budget section**: Design docs are included in the context budget and trimmed after conventions, before file context, if the total exceeds the model's context limit.
|
- **`DesignDocs` budget section**: Design docs are included in the context budget and trimmed after conventions, before file context, if the total exceeds the model's context limit.
|
||||||
|
|||||||
+52
-80
@@ -1,96 +1,68 @@
|
|||||||
# Dev Loop Status — 2026-05-15 09:37 UTC
|
# Dev Loop Status — 2026-05-15 11:58 UTC
|
||||||
|
|
||||||
## Summary
|
**Cron ID:** 5342ac81-4bbc-4e4c-a123-347a7788d50c
|
||||||
|
**Status:** ✅ HEALTHY — All tests passing, repo clean, ready for review & merge
|
||||||
|
|
||||||
- **Review-bot status:** ✅ MAIN BRANCH CURRENT & HEALTHY
|
## Quick Status
|
||||||
- **Coverage:** 77.1% (↑ from 70.4%) — healthy trajectory
|
|
||||||
- **Tests:** ✅ All passing
|
|
||||||
- **Active development tracks:**
|
|
||||||
- issue-143: fetch doc-map config from trusted VCS ref (ready for review)
|
|
||||||
- issue-146: reuse resolved doc-map path early (ready for review)
|
|
||||||
- issue-150: add EvalSymlinks to validateDocmapPath (ready for review)
|
|
||||||
- issue-154: refactor subprocess test helpers (ready for review)
|
|
||||||
|
|
||||||
---
|
- **Main branch:** Synced with origin/main (d855064)
|
||||||
|
- **Tests:** All passing ✅ (7 packages, 80+ test cases, race detector clean)
|
||||||
|
- **Test coverage:** **77.1%** overall
|
||||||
|
- budget: 92.0%
|
||||||
|
- review: 92.0%
|
||||||
|
- gitea: 85.2%
|
||||||
|
- github: 86.3%
|
||||||
|
- llm: 81.3%
|
||||||
|
- netutil: 85.7%
|
||||||
|
- cmd/review-bot: 54.3%
|
||||||
|
- **Working tree:** Clean (no uncommitted changes)
|
||||||
|
|
||||||
## Current State
|
## PR Status & Recommended Actions
|
||||||
|
|
||||||
### Main Branch
|
### Ready to Merge (3 PRs)
|
||||||
- **HEAD:** 1650343 (dev-loop cycle complete)
|
These have `ready` label, passing tests, and are self-reviewed. Recommend merging in order:
|
||||||
- **Status:** Clean, all tests passing, 77.1% coverage
|
|
||||||
- **Recent work:** Issue #130 fixes merged and verified complete
|
|
||||||
|
|
||||||
### Active Issue Branches (Ready for Review)
|
| Order | PR | Issue | Type | Size | Status |
|
||||||
|
|-------|----|----|------|------|--------|
|
||||||
|
| 1️⃣ | #155 | #154 | Refactor | M | ✅ Ready |
|
||||||
|
| 2️⃣ | #152 | #150 | Security | S | ✅ Ready |
|
||||||
|
| 3️⃣ | #151 | #146 | Test | S | ✅ Ready |
|
||||||
|
|
||||||
| Issue | Branch | Latest Commit | Status | Recommendation |
|
**Merge strategy:** Sequential. All currently passing; no blocking dependencies.
|
||||||
|-------|--------|---------------|--------|-----------------|
|
|
||||||
| #143 | origin/issue-143 | 3222c76 | Ready | Review feature + tests, consider for merge |
|
|
||||||
| #146 | origin/issue-146 | 9b64c60 | Ready | 2 new test cases + 1 fix, review completeness |
|
|
||||||
| #150 | origin/issue-150 | 4dce8e4 | Ready | Symlink validation, security-sensitive |
|
|
||||||
| #154 | origin/issue-154 | 2892dff | Ready | Refactor/cleanup, low-risk |
|
|
||||||
|
|
||||||
### Priority Assessment
|
### Awaiting AI-Review (2 PRs)
|
||||||
|
These have passing tests and self-review but need ai-review before marking ready:
|
||||||
|
|
||||||
**High Priority (Security/Risk):**
|
| PR | Issue | Type | Size | Notes |
|
||||||
- **#150** — EvalSymlinks for dir-symlink bypass (security fix)
|
|----|-------|------|------|-------|
|
||||||
- **#143** — Fetch doc-map from trusted VCS ref (trust boundary)
|
| #156 | #141 | Feature | M | `validate-docmap` subcommand |
|
||||||
|
| #153 | #143 | Feature | M | Fetch doc-map from VCS |
|
||||||
|
|
||||||
**Medium Priority (Feature):**
|
## Dev Loop Health
|
||||||
- **#146** — Path resolution optimization + tests
|
|
||||||
|
|
||||||
**Low Priority (Cleanup):**
|
| Metric | Status | Details |
|
||||||
- **#154** — Test refactoring
|
|--------|--------|---------|
|
||||||
|
| Main branch | ✅ Current | d855064 (2026-05-15 11:44 UTC) |
|
||||||
|
| Working tree | ✅ Clean | Ready for fetch/merge |
|
||||||
|
| Test suite | ✅ All pass | 7 packages, 80+ cases, ~2s runtime |
|
||||||
|
| Race detector | ✅ Clean | No race conditions detected |
|
||||||
|
| Coverage | ✅ 77.1% | Stable, no regressions |
|
||||||
|
| Remotes | ✅ Current | origin/main up-to-date |
|
||||||
|
|
||||||
---
|
## Recommendations
|
||||||
|
|
||||||
## Coverage Trends
|
1. **[IMMEDIATE] Merge 3 ready PRs** (#155 → #152 → #151)
|
||||||
|
- All provide foundational support for downstream features
|
||||||
|
- Safe to merge in sequence; no cross-PR dependencies
|
||||||
|
- Post-merge: dev-loop can run verification cycle
|
||||||
|
|
||||||
| Package | Current | Previous | Δ |
|
2. **Schedule AI-review for #156 and #153**
|
||||||
|---------|---------|----------|---|
|
- Both feature-complete and test-passing
|
||||||
| cmd/review-bot | TBD | 36.8% | ↑ |
|
- Waiting on code quality & design review
|
||||||
| budget | 91.8% | 91.8% | → |
|
|
||||||
| review | 91.5% | 91.5% | → |
|
|
||||||
| llm | 81.3% | 81.3% | → |
|
|
||||||
| **Total** | **77.1%** | **70.4%** | **↑6.7%** |
|
|
||||||
|
|
||||||
---
|
## Cycle Complete ✅
|
||||||
|
|
||||||
## Recommendations for Next Cycle
|
Next dev-loop cycle will:
|
||||||
|
- Verify post-merge state
|
||||||
### Immediate (This Dev-Loop)
|
- Update coverage tracking
|
||||||
1. **Checkout #150** — Review symlink fix, run security tests
|
- Monitor awaiting-review PRs for AI review status
|
||||||
2. **Checkout #143** — Review doc-map config fetching, validate error handling
|
|
||||||
3. **Decide merge order** — #150 or #143 first (dependency check)
|
|
||||||
4. **Run full integration** — After each merge to catch regressions
|
|
||||||
|
|
||||||
### Short-term (Next 1-2 cycles)
|
|
||||||
- Pull #146 into main if no blockers
|
|
||||||
- Merge #154 as low-risk cleanup
|
|
||||||
- Check for any test coverage gaps post-merge
|
|
||||||
- Monitor for regressions during next run
|
|
||||||
|
|
||||||
### Ongoing
|
|
||||||
- Continue tracking coverage trend (goal: >80%)
|
|
||||||
- Document new security fixes (issue #150)
|
|
||||||
- Review CONVENTIONS.md for consistency across new code
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Worktrees
|
|
||||||
|
|
||||||
- All stale worktrees cleaned in previous cycle ✅
|
|
||||||
- Ready for new worktree setup if Aaron wants to work on next issue
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Dev-Loop Cycle
|
|
||||||
|
|
||||||
When dev-loop runs next (in ~4 hours):
|
|
||||||
1. ✅ Verify main still current
|
|
||||||
2. ✅ Re-run tests & coverage
|
|
||||||
3. ✅ Check if any PRs merged (update local branches)
|
|
||||||
4. ⚠️ Flag for human review if coverage drops or tests fail
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
_Generated by dev-loop at 2026-05-15 09:37 UTC_
|
|
||||||
|
|||||||
@@ -210,7 +210,6 @@ AI Core handles OAuth token management and deployment discovery automatically. M
|
|||||||
| `system-prompt-file` | No | `""` | Local file with additional system prompt instructions |
|
| `system-prompt-file` | No | `""` | Local file with additional system prompt instructions |
|
||||||
| `doc-map` | No | `""` | Path to a YAML file mapping source path globs to governing design docs |
|
| `doc-map` | No | `""` | Path to a YAML file mapping source path globs to governing design docs |
|
||||||
| `doc-map-max-bytes` | No | `102400` | Maximum bytes of injected doc content from doc-map (default 100KB) |
|
| `doc-map-max-bytes` | No | `102400` | Maximum bytes of injected doc content from doc-map (default 100KB) |
|
||||||
| `doc-map-trusted-ref` | No | `""` | Git ref (e.g. `main`) to fetch the doc-map config from via VCS API instead of local workspace. **Recommended for security** — prevents a PR from modifying the doc-map config to inject arbitrary docs. |
|
|
||||||
| `persona` | No | `""` | Built-in persona name (security, architect, docs) |
|
| `persona` | No | `""` | Built-in persona name (security, architect, docs) |
|
||||||
| `persona-file` | No | `""` | Path to persona file (YAML or JSON) with custom review focus |
|
| `persona-file` | No | `""` | Path to persona file (YAML or JSON) with custom review focus |
|
||||||
| `temperature` | No | `0` | LLM temperature (0 = server default) |
|
| `temperature` | No | `0` | LLM temperature (0 = server default) |
|
||||||
|
|||||||
+7
-44
@@ -101,7 +101,6 @@ func main() {
|
|||||||
aicoreResourceGroup := flag.String("aicore-resource-group", envOrDefault("AICORE_RESOURCE_GROUP", "default"), "SAP AI Core resource group (for provider=aicore)")
|
aicoreResourceGroup := flag.String("aicore-resource-group", envOrDefault("AICORE_RESOURCE_GROUP", "default"), "SAP AI Core resource group (for provider=aicore)")
|
||||||
docMapFile := flag.String("doc-map", envOrDefault("DOC_MAP_FILE", ""), "Path to YAML file mapping source path globs to governing design docs")
|
docMapFile := flag.String("doc-map", envOrDefault("DOC_MAP_FILE", ""), "Path to YAML file mapping source path globs to governing design docs")
|
||||||
docMapMaxBytes := flag.Int("doc-map-max-bytes", envOrDefaultInt("DOC_MAP_MAX_BYTES", review.DefaultDocMapMaxBytes), "Maximum bytes of injected doc content (default 102400)")
|
docMapMaxBytes := flag.Int("doc-map-max-bytes", envOrDefaultInt("DOC_MAP_MAX_BYTES", review.DefaultDocMapMaxBytes), "Maximum bytes of injected doc content (default 102400)")
|
||||||
docMapTrustedRef := flag.String("doc-map-trusted-ref", envOrDefault("DOC_MAP_TRUSTED_REF", ""), "Git ref (e.g. main) to fetch the doc-map config from via VCS API instead of local workspace. Recommended to prevent PR branch from controlling which docs are injected.")
|
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@@ -369,45 +368,10 @@ func main() {
|
|||||||
// Step 6c: Load path-scoped design docs if doc-map specified
|
// Step 6c: Load path-scoped design docs if doc-map specified
|
||||||
designDocs := ""
|
designDocs := ""
|
||||||
if *docMapFile != "" {
|
if *docMapFile != "" {
|
||||||
var docMapCfg *review.DocMapConfig
|
docMapCfg, err := review.ParseDocMapConfig(resolvedDocMapFile)
|
||||||
|
if err != nil {
|
||||||
if *docMapTrustedRef != "" {
|
slog.Error("failed to parse doc-map file", "file", *docMapFile, "error", err)
|
||||||
// Fetch doc-map config from a trusted VCS ref (e.g. the default branch).
|
os.Exit(1)
|
||||||
// This prevents a malicious PR from modifying the doc-map config to
|
|
||||||
// inject arbitrary docs into the LLM prompt.
|
|
||||||
slog.Info("doc-map: fetching config from trusted ref",
|
|
||||||
"path", *docMapFile,
|
|
||||||
"ref", *docMapTrustedRef)
|
|
||||||
content, fetchErr := vcs.GetFileContentRef(ctx, owner, repoName, *docMapFile, *docMapTrustedRef)
|
|
||||||
if fetchErr != nil {
|
|
||||||
slog.Error("doc-map: failed to fetch config from trusted ref",
|
|
||||||
"path", *docMapFile,
|
|
||||||
"ref", *docMapTrustedRef,
|
|
||||||
"error", fetchErr)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
source := fmt.Sprintf("%s/%s@%s:%s", owner, repoName, *docMapTrustedRef, *docMapFile)
|
|
||||||
var parseErr error
|
|
||||||
docMapCfg, parseErr = review.ParseDocMapConfigContent(content, source)
|
|
||||||
if parseErr != nil {
|
|
||||||
slog.Error("doc-map: failed to parse fetched config",
|
|
||||||
"source", source,
|
|
||||||
"error", parseErr)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Local workspace fallback — the doc-map is read from the PR branch checkout.
|
|
||||||
// SECURITY WARNING: a malicious PR can modify this file to inject arbitrary
|
|
||||||
// docs. Set --doc-map-trusted-ref (or DOC_MAP_TRUSTED_REF) to a trusted ref
|
|
||||||
// (e.g. "main") to fetch the config from the default branch instead.
|
|
||||||
slog.Warn("doc-map: loading config from local workspace (PR branch) — " +
|
|
||||||
"set --doc-map-trusted-ref to fetch from a trusted ref for security")
|
|
||||||
var parseErr error
|
|
||||||
docMapCfg, parseErr = review.ParseDocMapConfig(resolvedDocMapFile)
|
|
||||||
if parseErr != nil {
|
|
||||||
slog.Error("failed to parse doc-map file", "file", *docMapFile, "error", parseErr)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect changed file paths from the PR for intersection.
|
// Collect changed file paths from the PR for intersection.
|
||||||
@@ -421,11 +385,10 @@ func main() {
|
|||||||
|
|
||||||
if len(matchedDocs) > 0 {
|
if len(matchedDocs) > 0 {
|
||||||
docMapOpts := review.DocMapOptions{MaxBytes: *docMapMaxBytes}
|
docMapOpts := review.DocMapOptions{MaxBytes: *docMapMaxBytes}
|
||||||
var loadErr error
|
designDocs, err = review.LoadMatchingDocs(ctx, vcs, owner, repoName, matchedDocs, docMapOpts)
|
||||||
designDocs, loadErr = review.LoadMatchingDocs(ctx, vcs, owner, repoName, matchedDocs, docMapOpts)
|
if err != nil {
|
||||||
if loadErr != nil {
|
|
||||||
// Non-fatal: individual missing files are already warned; log and continue.
|
// Non-fatal: individual missing files are already warned; log and continue.
|
||||||
slog.Warn("doc-map: partial failure loading docs", "error", loadErr)
|
slog.Warn("doc-map: partial failure loading docs", "error", err)
|
||||||
}
|
}
|
||||||
if designDocs != "" {
|
if designDocs != "" {
|
||||||
slog.Info("doc-map: injected design docs", "matched", len(matchedDocs), "bytes", len(designDocs))
|
slog.Info("doc-map: injected design docs", "matched", len(matchedDocs), "bytes", len(designDocs))
|
||||||
|
|||||||
@@ -1521,6 +1521,8 @@ func TestMainSubprocess_InvalidDocMapPath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(os.Args[0], "-test.run=TestMainSubprocess_InvalidDocMapPath")
|
cmd := exec.Command(os.Args[0], "-test.run=TestMainSubprocess_InvalidDocMapPath")
|
||||||
|
// t.TempDir() is evaluated here in the outer process, producing a real directory
|
||||||
|
// that is passed as the GITHUB_WORKSPACE env var string to the subprocess.
|
||||||
cmd.Env = append(cleanEnv(),
|
cmd.Env = append(cleanEnv(),
|
||||||
"TEST_SUBPROCESS_MAIN=1",
|
"TEST_SUBPROCESS_MAIN=1",
|
||||||
"GITHUB_WORKSPACE="+t.TempDir(),
|
"GITHUB_WORKSPACE="+t.TempDir(),
|
||||||
@@ -1558,6 +1560,8 @@ func TestMainSubprocess_InvalidDocMapFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(os.Args[0], "-test.run=TestMainSubprocess_InvalidDocMapFile")
|
cmd := exec.Command(os.Args[0], "-test.run=TestMainSubprocess_InvalidDocMapFile")
|
||||||
|
// t.TempDir() is evaluated here in the outer process, producing a real directory
|
||||||
|
// that is passed as the GITHUB_WORKSPACE env var string to the subprocess.
|
||||||
cmd.Env = append(cleanEnv(),
|
cmd.Env = append(cleanEnv(),
|
||||||
"TEST_SUBPROCESS_MAIN=1",
|
"TEST_SUBPROCESS_MAIN=1",
|
||||||
"GITHUB_WORKSPACE="+t.TempDir(),
|
"GITHUB_WORKSPACE="+t.TempDir(),
|
||||||
|
|||||||
+2
-18
@@ -52,31 +52,15 @@ func ParseDocMapConfig(localPath string) (*DocMapConfig, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read doc-map file %q: %w", localPath, err)
|
return nil, fmt.Errorf("read doc-map file %q: %w", localPath, err)
|
||||||
}
|
}
|
||||||
return parseDocMapBytes(data, localPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseDocMapConfigContent parses a doc-map YAML config from an in-memory
|
|
||||||
// string. The source parameter is used only for error messages and log entries
|
|
||||||
// (e.g. "main:main@<ref>").
|
|
||||||
//
|
|
||||||
// Use this when the config content has been fetched from a trusted VCS ref
|
|
||||||
// rather than read from the local workspace.
|
|
||||||
func ParseDocMapConfigContent(content, source string) (*DocMapConfig, error) {
|
|
||||||
data := []byte(content)
|
|
||||||
return parseDocMapBytes(data, source)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseDocMapBytes is the shared YAML parse implementation used by
|
|
||||||
// ParseDocMapConfig and ParseDocMapConfigContent.
|
|
||||||
func parseDocMapBytes(data []byte, source string) (*DocMapConfig, error) {
|
|
||||||
var cfg DocMapConfig
|
var cfg DocMapConfig
|
||||||
if err := yaml.UnmarshalWithOptions(data, &cfg, yaml.Strict()); err != nil {
|
if err := yaml.UnmarshalWithOptions(data, &cfg, yaml.Strict()); err != nil {
|
||||||
// Re-parse without strict mode to log which keys are unknown.
|
// Re-parse without strict mode to log which keys are unknown.
|
||||||
var relaxed DocMapConfig
|
var relaxed DocMapConfig
|
||||||
if err2 := yaml.Unmarshal(data, &relaxed); err2 != nil {
|
if err2 := yaml.Unmarshal(data, &relaxed); err2 != nil {
|
||||||
return nil, fmt.Errorf("parse doc-map YAML %q: %w", source, err)
|
return nil, fmt.Errorf("parse doc-map YAML %q: %w", localPath, err)
|
||||||
}
|
}
|
||||||
slog.Warn("doc-map YAML contains unknown keys (ignored)", "file", source, "error", err)
|
slog.Warn("doc-map YAML contains unknown keys (ignored)", "file", localPath, "error", err)
|
||||||
cfg = relaxed
|
cfg = relaxed
|
||||||
}
|
}
|
||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
|
|||||||
@@ -510,63 +510,3 @@ func TestFileCoveredByDocMap_EmptyConfig(t *testing.T) {
|
|||||||
t.Error("expected false for empty config, got true")
|
t.Error("expected false for empty config, got true")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// ParseDocMapConfigContent
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
func TestParseDocMapConfigContent_Valid(t *testing.T) {
|
|
||||||
content := `
|
|
||||||
mappings:
|
|
||||||
- paths:
|
|
||||||
- "lib/foo/**"
|
|
||||||
docs:
|
|
||||||
- docs/foo.md
|
|
||||||
`
|
|
||||||
cfg, err := ParseDocMapConfigContent(content, "owner/repo@main:.review-bot/doc-map.yml")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if len(cfg.Mappings) != 1 {
|
|
||||||
t.Fatalf("expected 1 mapping, got %d", len(cfg.Mappings))
|
|
||||||
}
|
|
||||||
if len(cfg.Mappings[0].Docs) != 1 || cfg.Mappings[0].Docs[0] != "docs/foo.md" {
|
|
||||||
t.Errorf("unexpected mapping: %+v", cfg.Mappings[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseDocMapConfigContent_EmptyContent(t *testing.T) {
|
|
||||||
cfg, err := ParseDocMapConfigContent("", "test-source")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error for empty content: %v", err)
|
|
||||||
}
|
|
||||||
if len(cfg.Mappings) != 0 {
|
|
||||||
t.Errorf("expected 0 mappings for empty content, got %d", len(cfg.Mappings))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseDocMapConfigContent_InvalidYAML(t *testing.T) {
|
|
||||||
_, err := ParseDocMapConfigContent("mappings: [{{invalid", "test-source")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected error for invalid YAML, got nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseDocMapConfigContent_UnknownKeys(t *testing.T) {
|
|
||||||
content := `
|
|
||||||
mappings:
|
|
||||||
- paths:
|
|
||||||
- "lib/**"
|
|
||||||
docs:
|
|
||||||
- docs/foo.md
|
|
||||||
unknown_top_level_key: "should be warned but not fatal"
|
|
||||||
`
|
|
||||||
// Unknown top-level keys produce a warning but not an error.
|
|
||||||
cfg, err := ParseDocMapConfigContent(content, "test-source")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error for unknown keys: %v", err)
|
|
||||||
}
|
|
||||||
if len(cfg.Mappings) == 0 {
|
|
||||||
t.Error("expected mappings to be parsed despite unknown key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user