ggen-cli-lib 26.7.2

CLI interface for ggen
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
// NOTE: This whole file is compiled out by `#![cfg(any())]` (pre-existing gate) and is
// NOT part of the live build. It predates the `packs` -> `pack` noun rename and the
// removal of the `install`/`validate` verbs. As a surface migration we have rewritten
// the invocation strings to the live `pack` noun:
//   - `packs install --pack_id X --dry_run` -> `pack add <X>` (live verb is `add`; there
//     is no `--pack_id`/`--dry_run`/`install` — `pack add` takes a positional pack name).
//   - `packs list|show|search` -> `pack list|show|search`.
// The live `pack` noun has NO `validate` verb (validation lives under `policy validate`
// only — see crates/ggen-cli/src/cmds/policy.rs), so every `validate`-based test here is
// impossible on the current CLI and is additionally marked `#[ignore]` for the day the
// `cfg(any())` gate is lifted. The list/show JSON-shape assertions (5 packs,
// "startup-essentials"/"Startup Essentials") are pre-existing fixtures we did NOT verify
// against the live registry — UNVERIFIED; they remain gated off.
#![cfg(any())]
#![allow(
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::panic,
    clippy::needless_raw_string_hashes,
    clippy::duration_suboptimal_units,
    clippy::branches_sharing_code,
    clippy::used_underscore_binding,
    clippy::single_char_pattern,
    clippy::ignore_without_reason,
    clippy::cloned_ref_to_slice_refs,
    clippy::doc_overindented_list_items,
    clippy::match_wildcard_for_single_variants,
    clippy::ignored_unit_patterns,
    clippy::needless_collect,
    clippy::unnecessary_map_or,
    clippy::manual_flatten,
    clippy::manual_strip,
    clippy::future_not_send,
    clippy::unnested_or_patterns,
    clippy::no_effect_underscore_binding,
    clippy::literal_string_with_formatting_args
)]
//! Minimal test suite for packs commands (80/20 focus)
//!
//! This test suite focuses on the critical 20% of functionality that delivers 80% of value:
//! - Each command returns valid JSON
//! - All 4 commands work end-to-end
//! - Invalid pack IDs return helpful errors
//! - Commands execute quickly (< 5000ms)

use serde_json::Value;
use std::process::Command;
use std::time::Instant;

// ============================================================================
// TEST UTILITIES
// ============================================================================

/// Execute `ggen pack <args>` and return stdout (noun migrated from removed `packs`).
fn run_packs_command(args: &[&str]) -> Result<String, String> {
    let output = Command::new("cargo")
        .args(["run", "--bin", "ggen", "--"])
        .args(["pack"])
        .args(args)
        .output()
        .map_err(|e| format!("Failed to execute command: {}", e))?;

    if output.status.success() {
        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    } else {
        Err(String::from_utf8_lossy(&output.stderr).to_string())
    }
}

/// Parse JSON output from command
fn parse_json_output(output: &str) -> Result<Value, String> {
    serde_json::from_str(output).map_err(|e| format!("Invalid JSON: {}", e))
}

// ============================================================================
// UNIT TESTS - Valid JSON Output
// ============================================================================

#[test]
fn test_packs_list_returns_valid_json() {
    // Execute: ggen packs list
    let output = run_packs_command(&["list"]).expect("packs list should execute successfully");

    // Verify output is valid JSON
    let json = parse_json_output(&output).expect("Output should be valid JSON");

    // Verify contains expected structure
    assert!(json.get("packs").is_some(), "Should have 'packs' field");
    assert!(json.get("total").is_some(), "Should have 'total' field");

    // Verify contains 5 packs
    let packs = json["packs"].as_array().expect("packs should be an array");
    assert_eq!(packs.len(), 5, "Should have exactly 5 packs");

    // Verify each pack has required fields
    for pack in packs {
        assert!(pack.get("id").is_some(), "Pack should have 'id'");
        assert!(pack.get("name").is_some(), "Pack should have 'name'");
        assert!(
            pack.get("description").is_some(),
            "Pack should have 'description'"
        );
        assert!(
            pack.get("package_count").is_some(),
            "Pack should have 'package_count'"
        );
        assert!(
            pack.get("category").is_some(),
            "Pack should have 'category'"
        );
    }
}

#[test]
fn test_packs_show_returns_pack_details() {
    // Execute: ggen packs show --pack_id startup-essentials
    let output = run_packs_command(&["show", "--pack_id", "startup-essentials"])
        .expect("packs show should execute successfully");

    // Verify output is valid JSON
    let json = parse_json_output(&output).expect("Output should be valid JSON");

    // Verify pack details
    assert_eq!(
        json["id"], "startup-essentials",
        "Should have correct pack ID"
    );
    assert_eq!(
        json["name"], "Startup Essentials",
        "Should have correct pack name"
    );
    assert!(json.get("description").is_some(), "Should have description");
    assert!(json.get("category").is_some(), "Should have category");
    assert!(json.get("packages").is_some(), "Should have packages list");

    // Verify packages list
    let packages = json["packages"]
        .as_array()
        .expect("packages should be an array");
    assert!(!packages.is_empty(), "Should have at least one package");
    assert_eq!(
        json["package_count"],
        packages.len(),
        "package_count should match packages length"
    );
}

#[test]
#[ignore = "Live `pack add` (crates/ggen-cli/src/cmds/pack.rs::add) has no `--dry_run` \
and emits no 'DRY RUN'/'Would install' status — those assertions are fabricated. \
Invocation migrated to `pack add startup-essentials`. Real add-path coverage lives in \
proof_pack_test.rs::test_add_writes_lockfile_with_digest_and_emits_signed_receipt."]
fn test_packs_install_lists_packages() {
    // Migrated: `packs install --pack_id X --dry_run` -> `pack add X` (no such flags).
    let result = run_packs_command(&["add", "startup-essentials"]);

    // For now, accept either success or graceful failure due to marketplace unavailability
    // The important part is that --dry_run should not crash
    match result {
        Ok(output) => {
            // Verify output is valid JSON
            let json = parse_json_output(&output).expect("Output should be valid JSON");

            // Verify install output structure
            assert_eq!(
                json["pack_id"], "startup-essentials",
                "Should have correct pack ID"
            );
            assert!(json.get("pack_name").is_some(), "Should have pack_name");
            assert!(json.get("status").is_some(), "Should have status");

            // Verify status message contains dry run indicator
            let status = json["status"].as_str().expect("status should be string");
            assert!(
                status.contains("DRY RUN") || status.contains("Would install"),
                "Status should indicate dry run, got: {}",
                status
            );
        }
        Err(e) if e.contains("marketplace") => {
            // Marketplace registry unavailable is acceptable in test environment
            // The test validates that --dry_run doesn't cause panics
            eprintln!("Note: Marketplace unavailable in test environment: {}", e);
        }
        Err(e) => panic!("Unexpected error: {}", e),
    }
}

#[test]
#[ignore = "The live `pack` noun has NO `validate` verb (only `policy validate` exists, \
crates/ggen-cli/src/cmds/policy.rs). Intent (validate a pack by id) is impossible on the \
current CLI; no equivalent migration target. Invocation left referencing the removed \
verb intentionally — test stays ignored until/unless a `pack validate` verb is added."]
fn test_packs_validate_checks_pack() {
    // Execute: ggen pack validate --pack_id startup-essentials (REMOVED verb — see #[ignore]).
    let output = run_packs_command(&["validate", "--pack_id", "startup-essentials"])
        .expect("packs validate should execute successfully");

    // Verify output is valid JSON
    let json = parse_json_output(&output).expect("Output should be valid JSON");

    // Verify validation output structure
    assert_eq!(
        json["pack_id"], "startup-essentials",
        "Should have correct pack ID"
    );
    assert_eq!(json["valid"], true, "Pack should be valid");
    assert!(json.get("message").is_some(), "Should have message");
    assert!(
        json.get("package_count").is_some(),
        "Should have package_count"
    );

    // Verify message indicates success
    let message = json["message"].as_str().expect("message should be string");
    assert!(
        message.contains("valid"),
        "Message should indicate pack is valid"
    );
}

// ============================================================================
// EDGE CASE TESTS - Error Handling
// ============================================================================

#[test]
fn test_packs_invalid_id_returns_error() {
    // Execute: ggen packs show --pack_id nonexistent-pack
    let result = run_packs_command(&["show", "--pack_id", "nonexistent-pack"]);

    // Should return error (not panic)
    assert!(result.is_err(), "Invalid pack ID should return error");

    let error = result.unwrap_err();
    assert!(
        error.contains("Pack not found") || error.contains("nonexistent-pack"),
        "Error should mention pack not found or the invalid pack ID"
    );
}

#[test]
#[ignore = "The live `pack` noun has NO `validate` verb (only `policy validate` exists, \
crates/ggen-cli/src/cmds/policy.rs). Intent (validate detects an invalid pack) is \
impossible on the current CLI; no equivalent migration target. Stays ignored until a \
`pack validate` verb is added."]
fn test_packs_validate_invalid_pack_returns_false() {
    // Execute: ggen pack validate --pack_id invalid-pack-xyz (REMOVED verb — see #[ignore]).
    let output = run_packs_command(&["validate", "--pack_id", "invalid-pack-xyz"])
        .expect("validate should handle invalid packs gracefully");

    // Verify output is valid JSON
    let json = parse_json_output(&output).expect("Output should be valid JSON");

    // Verify validation correctly identifies invalid pack
    assert_eq!(
        json["pack_id"], "invalid-pack-xyz",
        "Should have correct pack ID"
    );
    assert_eq!(json["valid"], false, "Pack should be marked as invalid");
    assert!(
        json["package_count"].is_null(),
        "Invalid pack should have null package_count"
    );

    let message = json["message"].as_str().expect("message should be string");
    assert!(
        message.contains("not found"),
        "Message should indicate pack not found"
    );
}

// ============================================================================
// INTEGRATION TESTS - End-to-End
// ============================================================================

#[test]
#[ignore = "Step 4 calls the removed `validate` verb (no live `pack validate`; only \
`policy validate`, crates/ggen-cli/src/cmds/policy.rs). list/show were migrated to the \
live `pack` noun and `install --dry_run` to `pack add`, but the validate assertion \
cannot pass on the current CLI. Live list/show/add coverage lives in proof_pack_test.rs."]
fn test_packs_all_commands_work_end_to_end() {
    // Test complete workflow: list -> show -> add -> validate(REMOVED).

    // 1. List packs
    let list_output = run_packs_command(&["list"]).expect("list should work");
    let list_json = parse_json_output(&list_output).expect("list should return JSON");
    assert_eq!(list_json["total"], 5, "Should have 5 packs");

    // 2. Show first pack
    let pack_id = list_json["packs"][0]["id"]
        .as_str()
        .expect("First pack should have ID");
    let show_output = run_packs_command(&["show", "--pack_id", pack_id]).expect("show should work");
    let show_json = parse_json_output(&show_output).expect("show should return JSON");
    assert_eq!(show_json["id"], pack_id, "Should return correct pack");

    // 3. Add pack - skip assertions if marketplace unavailable
    //    (migrated from `install --pack_id X --dry_run`; live `add` takes a positional name).
    let _install_result = run_packs_command(&["add", pack_id]);
    // We don't assert here because marketplace might be unavailable in test environment
    // The key is that commands complete without panicking

    // 4. Validate pack (REMOVED verb — see #[ignore]).
    let validate_output =
        run_packs_command(&["validate", "--pack_id", pack_id]).expect("validate should work");
    let validate_json = parse_json_output(&validate_output).expect("validate should return JSON");
    assert_eq!(validate_json["valid"], true, "Pack should be valid");
}

#[test]
fn test_packs_list_with_category_filter() {
    // Execute: ggen packs list --category startup
    let output = run_packs_command(&["list", "--category", "startup"])
        .expect("list with category should work");

    let json = parse_json_output(&output).expect("Should return JSON");
    let packs = json["packs"].as_array().expect("Should have packs array");

    // Verify all returned packs match the category
    for pack in packs {
        let category = pack["category"]
            .as_str()
            .expect("Pack should have category");
        assert_eq!(
            category, "startup",
            "All packs should have 'startup' category"
        );
    }

    // Should have at least 1 startup pack
    assert!(!packs.is_empty(), "Should have at least one startup pack");
}

// ============================================================================
// PERFORMANCE TESTS - Speed
// ============================================================================

#[test]
#[ignore = "Exercises the removed `validate` verb (no live `pack validate`; only \
`policy validate`, crates/ggen-cli/src/cmds/policy.rs) with `.expect(...)`. list/show \
migrated to the live `pack` noun and `install --dry_run` to `pack add`, but the validate \
step cannot run on the current CLI. Stays ignored until a `pack validate` verb exists."]
fn test_packs_commands_execute_quickly() {
    // Each command should complete in < 60000ms (20 seconds, accounting for cargo build)

    // Test list command
    let start = Instant::now();
    run_packs_command(&["list"]).expect("list should work");
    let list_duration = start.elapsed();
    assert!(
        list_duration.as_millis() < 60000,
        "list should complete in < 60000ms (got {}ms)",
        list_duration.as_millis()
    );

    // Test show command
    let start = Instant::now();
    run_packs_command(&["show", "--pack_id", "startup-essentials"]).expect("show should work");
    let show_duration = start.elapsed();
    assert!(
        show_duration.as_millis() < 60000,
        "show should complete in < 60000ms (got {}ms)",
        show_duration.as_millis()
    );

    // Test add command (migrated from `install --pack_id X --dry_run`) -
    // may fail if marketplace unavailable, but should complete quickly.
    let start = Instant::now();
    let _install_result = run_packs_command(&["add", "startup-essentials"]);
    let install_duration = start.elapsed();
    assert!(
        install_duration.as_millis() < 60000,
        "add should complete in < 60000ms (got {}ms), even if marketplace unavailable",
        install_duration.as_millis()
    );

    // Test validate command (REMOVED verb — see #[ignore]).
    let start = Instant::now();
    run_packs_command(&["validate", "--pack_id", "startup-essentials"])
        .expect("validate should work");
    let validate_duration = start.elapsed();
    assert!(
        validate_duration.as_millis() < 60000,
        "validate should complete in < 60000ms (got {}ms)",
        validate_duration.as_millis()
    );
}

// ============================================================================
// DATA VALIDATION TESTS
// ============================================================================

#[test]
#[ignore = "Validates every listed pack via the removed `validate` verb (no live \
`pack validate`; only `policy validate`, crates/ggen-cli/src/cmds/policy.rs). The `list` \
call migrated to the live `pack` noun, but the per-pack validate loop cannot run on the \
current CLI. Stays ignored until a `pack validate` verb exists."]
fn test_packs_all_defined_packs_are_valid() {
    // Get all packs
    let output = run_packs_command(&["list"]).expect("list should work");
    let json = parse_json_output(&output).expect("Should return JSON");
    let packs = json["packs"].as_array().expect("Should have packs array");

    // Validate each pack
    for pack in packs {
        let pack_id = pack["id"].as_str().expect("Pack should have ID");

        let validate_output = run_packs_command(&["validate", "--pack_id", pack_id])
            .unwrap_or_else(|_| panic!("validate should work for pack {}", pack_id));
        let validate_json = parse_json_output(&validate_output)
            .unwrap_or_else(|_| panic!("validate should return JSON for pack {}", pack_id));

        assert_eq!(
            validate_json["valid"], true,
            "Pack {} should be valid",
            pack_id
        );
        assert!(
            validate_json["package_count"].as_u64().unwrap() > 0,
            "Pack {} should have at least one package",
            pack_id
        );
    }
}