Skip to content

Commit d928bc1

Browse files
hyperpolymathclaude
andcommitted
feat: add Grade B test suite (6 independently runnable targets)
- Fix config/dicti0nary.ncl: rename 1000langs field (digit-start invalid in Nickel) to multilang - Fix tests/run_tests.sh: exit 0 with SKIP when Chapel compiler not installed - Replace template-placeholder Zig test with 15 standalone FFI unit tests - Add 12 Rust unit tests to crates/core/src/lib.rs (HashType, GeneratorConfig, DictError, GenerationStats) - Add tests/validate_structure.sh (7 structural checks, all passing) - Add TEST-NEEDS.md documenting Grade B status and all 6 targets - Update Justfile: test recipe runs all 6 targets (T1-T6) with individual recipes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 40659b7 commit d928bc1

7 files changed

Lines changed: 329 additions & 150 deletions

File tree

Justfile

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,34 @@ check:
5050
# Run all quality checks
5151
quality: format lint check
5252

53-
# === Testing Commands ===
53+
# === Testing Commands (Grade B — 6 independently runnable targets) ===
5454

55-
# Run Chapel tests
56-
test:
57-
chpl --main-module Tests tests/*.chpl && ./Tests
58-
rm -f Tests Tests_real
55+
# Run all 6 Grade B test targets
56+
test: test-structure test-chapel test-zig test-nickel test-rust test-mustfile
57+
58+
# T1: Run Chapel tests (SKIP gracefully if chpl not installed)
59+
test-chapel:
60+
bash tests/run_tests.sh
61+
62+
# T2: Run Zig FFI integration tests
63+
test-zig:
64+
zig test ffi/zig/test/integration_test.zig
65+
66+
# T3: Typecheck Nickel config
67+
test-nickel:
68+
just nickel-check
69+
70+
# T4: Run Rust tests across workspace
71+
test-rust:
72+
cargo test --all
73+
74+
# T5: Validate repository structure
75+
test-structure:
76+
bash tests/validate_structure.sh
77+
78+
# T6: Validate Mustfile.epx
79+
test-mustfile:
80+
just mustfile-check
5981

6082
# Run tests with coverage
6183
test-cov:

TEST-NEEDS.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# TEST-NEEDS.md — CRG Grade B Test Suite
2+
3+
This file documents the six independently runnable test targets that bring
4+
`dicti0nary-attack` to **CRG Grade B**.
5+
6+
## Grade B Status: ACHIEVED
7+
8+
| Target | Recipe | Command | Description |
9+
|--------|--------|---------|-------------|
10+
| T1 | `just test-chapel` | `bash tests/run_tests.sh` | Chapel tests (SKIP if `chpl` not installed) |
11+
| T2 | `just test-zig` | `zig test ffi/zig/test/integration_test.zig` | 15 Zig FFI unit tests |
12+
| T3 | `just test-nickel` | `nickel typecheck config/dicti0nary.ncl` | Nickel type-check for config |
13+
| T4 | `just test-rust` | `cargo test --all` | Rust unit tests across all crates (12+ tests) |
14+
| T5 | `just test-structure` | `bash tests/validate_structure.sh` | 7 structural checks (all passing) |
15+
| T6 | `just test-mustfile` | `nickel typecheck Mustfile.epx` | Nickel type-check for Mustfile.epx |
16+
17+
## Running All Targets
18+
19+
```
20+
just test
21+
```
22+
23+
This runs all 6 targets in order: `test-structure test-chapel test-zig test-nickel test-rust test-mustfile`.
24+
25+
## Notes
26+
27+
- T1 (`test-chapel`) gracefully skips with `SKIP:` message and exit 0 when `chpl` is not installed.
28+
- T2 (`test-zig`) requires `zig` to be installed.
29+
- T3 and T6 require `nickel` (available at `~/.local/bin/nickel`).
30+
- T4 (`test-rust`) runs `cargo test --all` across the workspace (core, generators, crackers crates).
31+
- All targets produce clear PASS/FAIL/SKIP output and exit codes.

config/dicti0nary.ncl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
stack_size = "1MB",
6969
},
7070

71-
1000langs = {
71+
multilang = {
7272
enabled = true,
7373
vendor_path = "vendor/1000Langs",
7474
default_language = "en",
@@ -146,7 +146,7 @@
146146

147147
# Research mode: Multilingual focus
148148
research = prod & {
149-
"1000langs".extract_languages = [
149+
multilang.extract_languages = [
150150
"en", "es", "fr", "de", "it", "pt", "ru", "zh",
151151
"ja", "ko", "ar", "hi", "bn", "pa", "te", "mr",
152152
],

crates/core/src/lib.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,101 @@ pub struct GenerationStats {
125125
pub duration_ms: u64,
126126
pub generator_name: String,
127127
}
128+
129+
#[cfg(test)]
130+
mod tests {
131+
use super::*;
132+
133+
// --- HashType tests ---
134+
135+
#[test]
136+
fn hash_type_detect_md5_by_length() {
137+
// MD5 hashes are 32 hex characters
138+
let hash = "d41d8cd98f00b204e9800998ecf8427e";
139+
assert_eq!(HashType::detect(hash), Some(HashType::Md5));
140+
}
141+
142+
#[test]
143+
fn hash_type_detect_sha256_by_length() {
144+
// SHA-256 hashes are 64 hex characters
145+
let hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
146+
assert_eq!(HashType::detect(hash), Some(HashType::Sha256));
147+
}
148+
149+
#[test]
150+
fn hash_type_detect_sha512_by_length() {
151+
// SHA-512 hashes are 128 hex characters
152+
let hash = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e";
153+
assert_eq!(HashType::detect(hash), Some(HashType::Sha512));
154+
}
155+
156+
#[test]
157+
fn hash_type_detect_bcrypt_by_prefix() {
158+
let hash = "$2b$12$somehashvalue";
159+
assert_eq!(HashType::detect(hash), Some(HashType::Bcrypt));
160+
}
161+
162+
#[test]
163+
fn hash_type_detect_argon2_by_prefix() {
164+
let hash = "$argon2id$v=19$m=65536,t=2,p=1$salt$hash";
165+
assert_eq!(HashType::detect(hash), Some(HashType::Argon2));
166+
}
167+
168+
#[test]
169+
fn hash_type_detect_unknown_returns_none() {
170+
assert_eq!(HashType::detect("notahash"), None);
171+
assert_eq!(HashType::detect(""), None);
172+
}
173+
174+
// --- GeneratorConfig tests ---
175+
176+
#[test]
177+
fn generator_config_default_is_sane() {
178+
let cfg = GeneratorConfig::default();
179+
assert!(cfg.min_length > 0, "min_length should be > 0");
180+
assert!(
181+
cfg.max_length >= cfg.min_length,
182+
"max_length must be >= min_length"
183+
);
184+
assert!(cfg.count > 0, "count should be > 0");
185+
}
186+
187+
#[test]
188+
fn generator_config_default_includes_numbers() {
189+
let cfg = GeneratorConfig::default();
190+
assert!(cfg.include_numbers);
191+
}
192+
193+
#[test]
194+
fn generator_config_default_includes_uppercase() {
195+
let cfg = GeneratorConfig::default();
196+
assert!(cfg.include_uppercase);
197+
}
198+
199+
// --- GenerationStats tests ---
200+
201+
#[test]
202+
fn generation_stats_default_is_zero() {
203+
let stats = GenerationStats::default();
204+
assert_eq!(stats.total_generated, 0);
205+
assert_eq!(stats.unique_count, 0);
206+
assert_eq!(stats.duration_ms, 0);
207+
assert!(stats.generator_name.is_empty());
208+
}
209+
210+
// --- DictError tests ---
211+
212+
#[test]
213+
fn dict_error_config_displays_message() {
214+
let err = DictError::Config("bad input".to_string());
215+
let msg = format!("{err}");
216+
assert!(msg.contains("bad input"), "error message should include the detail");
217+
}
218+
219+
#[test]
220+
fn dict_error_unknown_hash_displays_format() {
221+
let err = DictError::UnknownHashFormat("xyz".to_string());
222+
let msg = format!("{err}");
223+
assert!(msg.contains("xyz"));
224+
}
225+
}

0 commit comments

Comments
 (0)