Skip to content

Commit a263a1f

Browse files
authored
feat: v0.9 Testing Infrastructure (#58)
* chore: add insta for snapshot testing, fix TOTP password leak Add insta with yaml feature to dev-dependencies for upcoming snapshot tests. Remove --password from the TOTP disable example in help text to avoid encouraging plaintext passwords on the command line. Replace literal "mypass" in tests with "placeholder". * feat(v0.9): add Docker Compose stack and scripts for E2E tests * feat(v0.9): add E2E test common module with setup and helpers * test(v0.9): add insta snapshot tests for auth, repo, and artifact commands * test(v0.9): add insta snapshot tests for governance commands * test(v0.9): add insta snapshot tests for security, federation, and admin commands * test(v0.9): add E2E tests for auth, repo, and admin commands * test(v0.9): add E2E tests for governance commands Cover group, permission, quality-gate, and label commands with integration tests that run against the E2E backend. Group and quality-gate lifecycle tests use the API to create resources (since show/delete require UUIDs) and verify CLI show/delete against them. * test(v0.9): add E2E tests for security, analytics, profile, webhook, and config * ci(v0.9): add E2E test job with Docker Compose backend * chore: bump version to 0.9.0, update sonar config and changelog * fix(v0.9): fix E2E test issues found during live backend testing - Use access_token field (not token) from login API response - Fix --type flag to --repo-type for repo create commands - Cache auth token with OnceLock to avoid rate limiting across tests * style: apply cargo fmt to E2E test files * ci: add CodeQL workflow with generated code exclusions Add explicit CodeQL advanced setup workflow that excludes sdk/src/generated_sdk.rs from analysis. The generated SDK file triggers false-positive "cleartext transmission" alerts since it makes HTTP calls by design.
1 parent 6a93b39 commit a263a1f

97 files changed

Lines changed: 2027 additions & 9 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/codeql/codeql-config.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name: "Artifact Keeper CLI CodeQL Config"
2+
3+
paths-ignore:
4+
- "sdk/src/generated_sdk.rs"
5+
- "target"
6+
- "tests/docker-compose.yml"

.github/workflows/ci.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,65 @@ jobs:
8484
- uses: dtolnay/rust-toolchain@stable
8585
- uses: Swatinem/rust-cache@v2
8686
- run: cargo build --release
87+
88+
e2e:
89+
name: E2E Tests
90+
runs-on: ubuntu-latest
91+
needs: [check]
92+
services:
93+
postgres:
94+
image: postgres:16
95+
env:
96+
POSTGRES_DB: artifact_registry_test
97+
POSTGRES_USER: registry
98+
POSTGRES_PASSWORD: registry
99+
ports:
100+
- 30433:5432
101+
options: >-
102+
--health-cmd "pg_isready -U registry -d artifact_registry_test"
103+
--health-interval 2s
104+
--health-timeout 5s
105+
--health-retries 15
106+
meilisearch:
107+
image: getmeili/meilisearch:v1.12
108+
env:
109+
MEILI_ENV: development
110+
ports:
111+
- 7701:7700
112+
options: >-
113+
--health-cmd "curl -f http://localhost:7700/health"
114+
--health-interval 2s
115+
--health-timeout 5s
116+
--health-retries 15
117+
steps:
118+
- uses: actions/checkout@v4
119+
- uses: dtolnay/rust-toolchain@stable
120+
- uses: Swatinem/rust-cache@v2
121+
- name: Start backend
122+
run: |
123+
docker run -d --name e2e-backend \
124+
--network ${{ job.services.postgres.network }} \
125+
-e DATABASE_URL="postgresql://registry:registry@postgres:5432/artifact_registry_test" \
126+
-e MEILI_URL="http://meilisearch:7700" \
127+
-e ADMIN_PASSWORD="admin123" \
128+
-e JWT_SECRET="e2e-test-secret-key-not-for-production" \
129+
-p 8081:8080 \
130+
ghcr.io/artifact-keeper/artifact-keeper-backend:latest
131+
- name: Wait for backend
132+
run: |
133+
for i in $(seq 1 60); do
134+
if curl -sf http://localhost:8081/health > /dev/null 2>&1; then
135+
echo "Backend healthy after $i attempts"
136+
exit 0
137+
fi
138+
sleep 2
139+
done
140+
docker logs e2e-backend
141+
exit 1
142+
- name: Run E2E tests
143+
env:
144+
E2E_BACKEND_URL: http://localhost:8081
145+
run: cargo test --test 'e2e_*' -- --include-ignored --test-threads=1
146+
- name: Backend logs (on failure)
147+
if: failure()
148+
run: docker logs e2e-backend

.github/workflows/codeql.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: "CodeQL"
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
schedule:
9+
- cron: "0 6 * * 1" # Monday 6am UTC
10+
11+
jobs:
12+
analyze:
13+
name: Analyze (${{ matrix.language }})
14+
runs-on: ubuntu-latest
15+
permissions:
16+
security-events: write
17+
contents: read
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
language: [actions, rust]
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- name: Initialize CodeQL
26+
uses: github/codeql-action/init@v3
27+
with:
28+
languages: ${{ matrix.language }}
29+
config-file: ./.github/codeql/codeql-config.yml
30+
31+
- if: matrix.language == 'rust'
32+
uses: dtolnay/rust-toolchain@stable
33+
34+
- name: Autobuild
35+
uses: github/codeql-action/autobuild@v3
36+
37+
- name: Perform CodeQL Analysis
38+
uses: github/codeql-action/analyze@v3
39+
with:
40+
category: "/language:${{ matrix.language }}"

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ All notable changes to the Artifact Keeper CLI (`ak`) will be documented in this
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.9.0] - 2026-02-21
9+
10+
### Added
11+
12+
- Snapshot tests using `insta` for JSON and table output regression detection across all command modules (~40 snapshot tests)
13+
- E2E integration test suite running against a real backend via Docker Compose (20+ test files covering auth, repo, admin, governance, analytics, webhook, and more)
14+
- Docker Compose stack (`tests/docker-compose.yml`) with backend, PostgreSQL, and Meilisearch for local E2E development
15+
- Shared test helpers in `tests/common/` for E2E environment setup, auth, and API access
16+
- CI pipeline job for automated E2E testing on push to main and PRs
17+
- Start/stop scripts (`tests/start-backend.sh`, `tests/stop-backend.sh`) for local E2E test development
18+
19+
### Fixed
20+
21+
- Removed plain-text password from TOTP command examples in CLI help text
22+
823
## [0.6.0] - 2026-02-21
924

1025
### Added

Cargo.lock

Lines changed: 22 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ members = ["sdk", "xtask"]
33

44
[package]
55
name = "artifact-keeper-cli"
6-
version = "0.8.0"
6+
version = "0.9.0"
77
edition = "2024"
88
description = "CLI/TUI tool for Artifact Keeper — an enterprise artifact registry"
99
license = "MIT"
@@ -69,7 +69,10 @@ futures = "0.3"
6969

7070
[dev-dependencies]
7171
assert_cmd = "2"
72+
insta = { version = "1", features = ["yaml"] }
7273
predicates = "3"
74+
reqwest = { version = "0.13", features = ["blocking", "json"] }
75+
serde_json = "1"
7376
tempfile = "3"
7477
wiremock = "0.6"
7578

sonar-project.properties

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
sonar.projectKey=artifact-keeper_artifact-keeper-cli
22
sonar.organization=artifact-keeper
33
sonar.sources=src
4-
sonar.tests=src
5-
sonar.test.inclusions=**/*test*.rs,**/*tests*.rs
6-
sonar.exclusions=target/**,sdk/**,**/tui.rs
4+
sonar.tests=src,tests
5+
sonar.test.inclusions=**/*test*.rs,**/*tests*.rs,tests/**/*.rs
6+
sonar.exclusions=target/**,sdk/**,**/tui.rs,tests/**
77
sonar.sourceEncoding=UTF-8

src/cli.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ pub enum Command {
317317

318318
/// Manage two-factor authentication (TOTP)
319319
#[command(
320-
after_help = "Examples:\n ak totp setup\n ak totp enable --code 123456\n ak totp disable --password mypass --code 123456\n ak totp status"
320+
after_help = "Examples:\n ak totp setup\n ak totp enable --code 123456\n ak totp disable --code 123456\n ak totp status"
321321
)]
322322
Totp {
323323
#[command(subcommand)]
@@ -2289,7 +2289,7 @@ mod tests {
22892289
"totp",
22902290
"disable",
22912291
"--password",
2292-
"mypass",
2292+
"placeholder",
22932293
"--code",
22942294
"654321",
22952295
])

src/commands/admin.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3084,4 +3084,39 @@ mod tests {
30843084
assert!(result.is_ok());
30853085
crate::test_utils::teardown_env();
30863086
}
3087+
3088+
// ---- insta snapshot tests ----
3089+
3090+
#[test]
3091+
fn snapshot_admin_user_list_json() {
3092+
let data = json!([{
3093+
"id": "00000000-0000-0000-0000-000000000001",
3094+
"username": "alice",
3095+
"email": "alice@example.com",
3096+
"display_name": "Alice Smith",
3097+
"is_admin": true,
3098+
"is_active": true,
3099+
"created_at": "2026-01-01T00:00:00Z",
3100+
"last_login_at": "2026-01-20T10:30:00Z"
3101+
}]);
3102+
let output = crate::output::render(&data, &OutputFormat::Json, None);
3103+
let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
3104+
insta::assert_yaml_snapshot!("admin_user_list_json", parsed);
3105+
}
3106+
3107+
#[test]
3108+
fn snapshot_admin_user_list_table() {
3109+
let items = vec![json!({
3110+
"id": "00000000-0000-0000-0000-000000000001",
3111+
"username": "alice",
3112+
"email": "alice@example.com",
3113+
"display_name": "Alice Smith",
3114+
"is_admin": true,
3115+
"is_active": true,
3116+
"created_at": "2026-01-01T00:00:00Z",
3117+
"last_login_at": "2026-01-20T10:30:00Z"
3118+
})];
3119+
let table = format_users_table(&items);
3120+
insta::assert_snapshot!("admin_user_list_table", table);
3121+
}
30873122
}

src/commands/analytics.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,4 +1140,22 @@ mod tests {
11401140

11411141
crate::test_utils::teardown_env();
11421142
}
1143+
1144+
// ---- insta snapshot tests ----
1145+
1146+
#[test]
1147+
fn snapshot_analytics_summary_json() {
1148+
let data = json!({
1149+
"total_artifacts": 1250,
1150+
"total_downloads": 45000,
1151+
"total_storage_bytes": 10737418240_i64,
1152+
"total_repositories": 12,
1153+
"active_users_30d": 42,
1154+
"top_format": "npm",
1155+
"period": "30d"
1156+
});
1157+
let output = crate::output::render(&data, &OutputFormat::Json, None);
1158+
let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
1159+
insta::assert_yaml_snapshot!("analytics_summary_json", parsed);
1160+
}
11431161
}

0 commit comments

Comments
 (0)