Skip to content

Commit dca0904

Browse files
committed
Merge branch 'auto/gardenlinux/4811' of ssh://github.com/gardenlinux/docs-ng into auto/gardenlinux/4811
2 parents f123f46 + 0867014 commit dca0904

12 files changed

Lines changed: 530 additions & 25 deletions

File tree

‎.github/workflows/tests-skip.yml‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Tests (skip)
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths-ignore:
7+
- 'src/**'
8+
- 'tests/**'
9+
- 'requirements.txt'
10+
- 'Makefile'
11+
- '.github/workflows/tests.yml'
12+
- '.github/workflows/tests-skip.yml'
13+
pull_request:
14+
branches: [main]
15+
paths-ignore:
16+
- 'src/**'
17+
- 'tests/**'
18+
- 'requirements.txt'
19+
- 'Makefile'
20+
- '.github/workflows/tests.yml'
21+
- '.github/workflows/tests-skip.yml'
22+
23+
jobs:
24+
pytest:
25+
name: pytest (unit + integration)
26+
runs-on: ubuntu-24.04
27+
steps:
28+
- run: |
29+
echo "No changes under src/, tests/, requirements.txt, or Makefile."
30+
echo "Skipping test run; reporting success so a required check is satisfied."

‎.github/workflows/tests.yml‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'src/**'
8+
- 'tests/**'
9+
- 'requirements.txt'
10+
- 'Makefile'
11+
- '.github/workflows/tests.yml'
12+
- '.github/workflows/tests-skip.yml'
13+
pull_request:
14+
branches: [main]
15+
paths:
16+
- 'src/**'
17+
- 'tests/**'
18+
- 'requirements.txt'
19+
- 'Makefile'
20+
- '.github/workflows/tests.yml'
21+
- '.github/workflows/tests-skip.yml'
22+
23+
jobs:
24+
pytest:
25+
name: pytest (unit + integration)
26+
runs-on: ubuntu-24.04
27+
steps:
28+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
29+
30+
- name: Set up Python
31+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # pin@v6.2.0
32+
with:
33+
python-version: "3.13"
34+
35+
- name: Install Python dependencies
36+
run: |
37+
pip install git+https://github.com/gardenlinux/glrd.git@v4.1.0
38+
pip install git+https://github.com/gardenlinux/python-gardenlinux-lib.git@0.10.20
39+
pip install -r requirements.txt
40+
41+
- name: Run tests
42+
run: make test

‎Makefile‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: help run dev build preview aggregate aggregate-repo test-aggregate-local clean clean-projects clean-aggregated-git test test-unit test-integration check spelling linkcheck woke
1+
.PHONY: help run build preview aggregate aggregate-repo test-aggregate-local clean clean-projects clean-aggregated-git test test-unit test-integration check spelling linkcheck woke
22

33
help:
44
@echo "Garden Linux Documentation Hub - Available targets:"
@@ -9,9 +9,9 @@ help:
99
@echo " preview - Preview production build locally"
1010
@echo ""
1111
@echo " Testing:"
12-
@echo " test - Run full test suite (38 tests: unit + integration)"
13-
@echo " test-unit - Run unit tests only (35 tests)"
14-
@echo " test-integration - Run integration tests only (3 tests)"
12+
@echo " test - Run full test suite (unit + integration)"
13+
@echo " test-unit - Run unit tests only"
14+
@echo " test-integration - Run integration tests only"
1515
@echo ""
1616
@echo " Quality Checks:"
1717
@echo " check - Run all quality checks (spelling, links, inclusive language)"
@@ -35,9 +35,9 @@ install:
3535
pnpm install
3636
pip install git+https://github.com/gardenlinux/glrd.git@v4.1.0
3737
pip install git+https://github.com/gardenlinux/python-gardenlinux-lib.git@0.10.20
38-
pip install -r requirements.txt
38+
pip install -r requirements.txt
3939

40-
dev:
40+
run:
4141
pnpm run docs:dev
4242

4343
build: install clean aggregate

‎docs/contributing/documentation/working-locally.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ This copies documentation from local repositories without using git.
7575
### Step 4: Start the Development Server
7676

7777
```bash
78-
make dev
78+
make run
7979
```
8080

8181
The documentation site will be available at `http://localhost:5173`.
@@ -146,7 +146,7 @@ If you encounter issues, try a clean build:
146146
```bash
147147
make clean
148148
make aggregate
149-
make dev
149+
make run
150150
```
151151

152152
### Check Dependencies

‎repos-config.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"docs_path": "docs",
77
"target_path": "projects/gardenlinux",
88
"ref": "docs/pxe_iso",
9-
"commit": "686463497e643a9b1e5427252a46cc80e3915c01",
9+
"commit": "b729180904c47be25baeb95ce5105e1f9ad8efba",
1010
"root_files": [
1111
"CONTRIBUTING.md",
1212
"SECURITY.md",

‎src/README.md‎

Lines changed: 0 additions & 1 deletion
This file was deleted.

‎src/aggregate.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ def main() -> int:
273273

274274
print("\nNext steps:")
275275
print(" 1. Review the changes in docs/projects/")
276-
print(" 2. Run 'make dev' or 'pnpm run docs:dev' to preview")
276+
print(" 2. Run 'make run' or 'pnpm run docs:dev' to preview")
277277
print(" 3. Commit the changes if satisfied")
278278

279279
return 0 if fail_count == 0 else 1

‎tests/integration/test_aggregation.py‎

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@
77
from aggregation import DocsFetcher, RepoConfig, process_all_markdown
88

99

10+
# ---------------------------------------------------------------------------
11+
# Shared helpers
12+
# ---------------------------------------------------------------------------
13+
14+
def _make_output_dir(tmp_path: Path) -> Path:
15+
"""Create and return a fresh output directory under tmp_path."""
16+
output_dir = tmp_path / "output"
17+
output_dir.mkdir(exist_ok=True)
18+
return output_dir
19+
20+
21+
def _make_fetcher(project_root: Path) -> DocsFetcher:
22+
"""Return a DocsFetcher anchored at project_root."""
23+
return DocsFetcher(project_root)
24+
25+
1026
class TestDocsFetcher:
1127
"""Integration tests for DocsFetcher."""
1228

@@ -31,14 +47,11 @@ def test_fetch_local_with_temp_dir(self, tmp_path):
3147
url=f"file://{repo_path}",
3248
docs_path="docs",
3349
target_path="projects/test-repo",
50+
ref="",
3451
)
3552

36-
# Fetch the docs
37-
output_dir = tmp_path / "output"
38-
output_dir.mkdir()
39-
40-
fetcher = DocsFetcher(tmp_path)
41-
result = fetcher.fetch(repo, output_dir)
53+
output_dir = _make_output_dir(tmp_path)
54+
result = _make_fetcher(tmp_path).fetch(repo, output_dir)
4255

4356
# Verify success
4457
assert result.success is True
@@ -52,6 +65,87 @@ def test_fetch_local_with_temp_dir(self, tmp_path):
5265
# Verify content
5366
assert "Test Documentation" in (output_dir / "index.md").read_text()
5467

68+
def test_fetch_local_missing_docs_path(self, tmp_path, capsys):
69+
"""Test that a missing docs_path results in success=True, no files copied, and a warning."""
70+
repo_path = tmp_path / "mock-repo"
71+
repo_path.mkdir(parents=True)
72+
# Intentionally do NOT create a 'docs' directory
73+
74+
repo = RepoConfig(
75+
name="test-repo",
76+
url=f"file://{repo_path}",
77+
docs_path="docs", # Does not exist
78+
target_path="projects/test-repo",
79+
ref="",
80+
)
81+
82+
output_dir = _make_output_dir(tmp_path)
83+
result = _make_fetcher(tmp_path).fetch(repo, output_dir)
84+
85+
# Fetch should still report success (warning is printed but not fatal)
86+
assert result.success is True
87+
# Output directory should be empty (nothing was copied)
88+
assert list(output_dir.iterdir()) == []
89+
# A warning about the missing docs_path must have been emitted to stdout
90+
captured = capsys.readouterr()
91+
assert "Warning" in captured.out
92+
assert "docs" in captured.out
93+
94+
def test_fetch_local_with_root_files_glob(self, tmp_path):
95+
"""Test that root_files with a glob pattern copies only matched files."""
96+
repo_path = tmp_path / "mock-repo"
97+
features_dir = repo_path / "features" / "foo"
98+
features_dir.mkdir(parents=True)
99+
(features_dir / "bar.md").write_text("# Feature Bar")
100+
(features_dir / "baz.md").write_text("# Feature Baz")
101+
# A file that should NOT match the glob
102+
(repo_path / "CHANGELOG.md").write_text("# Changes")
103+
104+
repo = RepoConfig(
105+
name="test-repo",
106+
url=f"file://{repo_path}",
107+
docs_path="nonexistent-docs", # No standard docs; only root_files
108+
target_path="projects/test-repo",
109+
ref="",
110+
root_files=["features/foo/*.md"],
111+
)
112+
113+
output_dir = _make_output_dir(tmp_path)
114+
result = _make_fetcher(tmp_path).fetch(repo, output_dir)
115+
116+
assert result.success is True
117+
# Glob-matched files should be present at their relative paths
118+
assert (output_dir / "features" / "foo" / "bar.md").exists()
119+
assert (output_dir / "features" / "foo" / "baz.md").exists()
120+
# Non-matching file should NOT be present
121+
assert not (output_dir / "CHANGELOG.md").exists()
122+
123+
def test_fetch_local_resolves_relative_path(self, tmp_path):
124+
"""Test that a relative file:// URL is resolved against project_root."""
125+
# Simulate a sibling-directory layout: project_root and mock-repo are siblings
126+
project_root = tmp_path / "docs-ng"
127+
project_root.mkdir()
128+
129+
repo_path = tmp_path / "mock-repo"
130+
docs_path = repo_path / "docs"
131+
docs_path.mkdir(parents=True)
132+
(docs_path / "index.md").write_text("# Hello from relative repo")
133+
134+
repo = RepoConfig(
135+
name="relative-repo",
136+
url="file://../mock-repo",
137+
docs_path="docs",
138+
target_path="projects/relative-repo",
139+
ref="",
140+
)
141+
142+
output_dir = _make_output_dir(tmp_path)
143+
result = _make_fetcher(project_root).fetch(repo, output_dir)
144+
145+
assert result.success is True
146+
assert (output_dir / "index.md").exists()
147+
assert "Hello from relative repo" in (output_dir / "index.md").read_text()
148+
55149

56150
class TestMarkdownProcessing:
57151
"""Integration tests for markdown processing."""

‎tests/unit/test_config.py‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ def test_save_multiple_repos(self, tmp_path):
144144
url="file://../repo2",
145145
docs_path="docs",
146146
target_path="projects/repo2",
147+
ref="",
147148
),
148149
]
149150
config_file = tmp_path / "config.json"
@@ -223,3 +224,103 @@ def test_round_trip(self, tmp_path):
223224
assert loaded_repos[0].url == original_repos[0].url
224225
assert loaded_repos[0].commit == original_repos[0].commit
225226
assert loaded_repos[0].root_files == original_repos[0].root_files
227+
228+
def test_load_config_rejects_invalid_scheme(self, tmp_path):
229+
"""Test that a repo with an invalid URL scheme causes SystemExit via validate()."""
230+
config_data = {
231+
"repos": [
232+
{
233+
"name": "bad",
234+
"url": "ftp://example.com/repo",
235+
"docs_path": "docs",
236+
"target_path": "projects/bad",
237+
"ref": "main",
238+
}
239+
]
240+
}
241+
config_file = tmp_path / "config.json"
242+
config_file.write_text(json.dumps(config_data))
243+
244+
with pytest.raises(SystemExit):
245+
load_config(str(config_file))
246+
247+
def test_save_preserves_structure_dict(self, tmp_path):
248+
"""Test that a dict structure survives a save/load round trip."""
249+
structure_map = {"old_dir": "new_dir", "another": "different"}
250+
repos = [
251+
RepoConfig(
252+
name="test-repo",
253+
url="https://github.com/test/repo",
254+
docs_path="docs",
255+
target_path="projects/test",
256+
ref="main",
257+
structure=structure_map,
258+
)
259+
]
260+
config_file = tmp_path / "config.json"
261+
262+
save_config(str(config_file), repos)
263+
loaded_repos = load_config(str(config_file))
264+
265+
assert loaded_repos[0].structure == structure_map
266+
267+
def test_save_omits_default_structure_flat(self, tmp_path):
268+
"""Test that structure='flat' is not written to the JSON output."""
269+
repos = [
270+
RepoConfig(
271+
name="test-repo",
272+
url="https://github.com/test/repo",
273+
docs_path="docs",
274+
target_path="projects/test",
275+
ref="main",
276+
)
277+
]
278+
config_file = tmp_path / "config.json"
279+
280+
save_config(str(config_file), repos)
281+
282+
with open(config_file) as f:
283+
data = json.load(f)
284+
285+
assert "structure" not in data["repos"][0]
286+
287+
def test_save_preserves_target_map_and_special_files(self, tmp_path):
288+
"""Test that target_map and special_files survive a save/load round trip."""
289+
repos = [
290+
RepoConfig(
291+
name="test-repo",
292+
url="https://github.com/test/repo",
293+
docs_path="docs",
294+
target_path="projects/test",
295+
ref="main",
296+
target_map={"src/api.md": "api/index.md"},
297+
special_files={"CHANGELOG.md": "reference/"},
298+
)
299+
]
300+
config_file = tmp_path / "config.json"
301+
302+
save_config(str(config_file), repos)
303+
loaded_repos = load_config(str(config_file))
304+
305+
assert loaded_repos[0].target_map == {"src/api.md": "api/index.md"}
306+
assert loaded_repos[0].special_files == {"CHANGELOG.md": "reference/"}
307+
308+
def test_save_local_repo_omits_empty_ref(self, tmp_path):
309+
"""Test that local repos with ref='' do not emit a 'ref' key in JSON."""
310+
repos = [
311+
RepoConfig(
312+
name="local-repo",
313+
url="file://../some-repo",
314+
docs_path="docs",
315+
target_path="projects/some-repo",
316+
ref="",
317+
)
318+
]
319+
config_file = tmp_path / "config.json"
320+
321+
save_config(str(config_file), repos)
322+
323+
with open(config_file) as f:
324+
data = json.load(f)
325+
326+
assert "ref" not in data["repos"][0]

0 commit comments

Comments
 (0)