red64-cli 0.3.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +194 -338
- package/dist/cli/parseArgs.d.ts.map +1 -1
- package/dist/cli/parseArgs.js +5 -13
- package/dist/cli/parseArgs.js.map +1 -1
- package/dist/components/init/types.d.ts +0 -2
- package/dist/components/init/types.d.ts.map +1 -1
- package/dist/components/screens/HelpScreen.d.ts.map +1 -1
- package/dist/components/screens/HelpScreen.js +0 -2
- package/dist/components/screens/HelpScreen.js.map +1 -1
- package/dist/components/screens/InitScreen.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.js +5 -8
- package/dist/components/screens/InitScreen.js.map +1 -1
- package/dist/components/screens/StartScreen.d.ts.map +1 -1
- package/dist/components/screens/StartScreen.js +29 -8
- package/dist/components/screens/StartScreen.js.map +1 -1
- package/dist/components/screens/StatusScreen.d.ts.map +1 -1
- package/dist/components/screens/StatusScreen.js +16 -1
- package/dist/components/screens/StatusScreen.js.map +1 -1
- package/dist/services/AgentInvoker.d.ts.map +1 -1
- package/dist/services/AgentInvoker.js +76 -37
- package/dist/services/AgentInvoker.js.map +1 -1
- package/dist/services/ClaudeErrorDetector.d.ts +1 -1
- package/dist/services/ClaudeErrorDetector.d.ts.map +1 -1
- package/dist/services/ClaudeErrorDetector.js +1 -0
- package/dist/services/ClaudeErrorDetector.js.map +1 -1
- package/dist/services/ClaudeHealthCheck.d.ts +7 -0
- package/dist/services/ClaudeHealthCheck.d.ts.map +1 -1
- package/dist/services/ClaudeHealthCheck.js +76 -12
- package/dist/services/ClaudeHealthCheck.js.map +1 -1
- package/dist/services/ConfigService.d.ts +1 -0
- package/dist/services/ConfigService.d.ts.map +1 -1
- package/dist/services/ConfigService.js.map +1 -1
- package/dist/services/DockerRunner.js +1 -1
- package/dist/services/DockerRunner.js.map +1 -1
- package/dist/services/PhaseExecutor.d.ts.map +1 -1
- package/dist/services/PhaseExecutor.js +2 -1
- package/dist/services/PhaseExecutor.js.map +1 -1
- package/dist/services/TaskRunner.d.ts.map +1 -1
- package/dist/services/TaskRunner.js +2 -1
- package/dist/services/TaskRunner.js.map +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/index.js.map +1 -1
- package/dist/types/index.d.ts +4 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/framework/stacks/c/code-quality.md +326 -0
- package/framework/stacks/c/coding-style.md +347 -0
- package/framework/stacks/c/conventions.md +513 -0
- package/framework/stacks/c/error-handling.md +350 -0
- package/framework/stacks/c/feedback.md +158 -0
- package/framework/stacks/c/memory-safety.md +408 -0
- package/framework/stacks/c/tech.md +122 -0
- package/framework/stacks/c/testing.md +472 -0
- package/framework/stacks/cpp/code-quality.md +282 -0
- package/framework/stacks/cpp/coding-style.md +363 -0
- package/framework/stacks/cpp/conventions.md +420 -0
- package/framework/stacks/cpp/error-handling.md +264 -0
- package/framework/stacks/cpp/feedback.md +104 -0
- package/framework/stacks/cpp/memory-safety.md +351 -0
- package/framework/stacks/cpp/tech.md +160 -0
- package/framework/stacks/cpp/testing.md +323 -0
- package/framework/stacks/java/code-quality.md +357 -0
- package/framework/stacks/java/coding-style.md +400 -0
- package/framework/stacks/java/conventions.md +437 -0
- package/framework/stacks/java/error-handling.md +408 -0
- package/framework/stacks/java/feedback.md +180 -0
- package/framework/stacks/java/tech.md +126 -0
- package/framework/stacks/java/testing.md +485 -0
- package/framework/stacks/javascript/async-patterns.md +216 -0
- package/framework/stacks/javascript/code-quality.md +182 -0
- package/framework/stacks/javascript/coding-style.md +293 -0
- package/framework/stacks/javascript/conventions.md +268 -0
- package/framework/stacks/javascript/error-handling.md +216 -0
- package/framework/stacks/javascript/feedback.md +80 -0
- package/framework/stacks/javascript/tech.md +114 -0
- package/framework/stacks/javascript/testing.md +209 -0
- package/framework/stacks/loco/code-quality.md +156 -0
- package/framework/stacks/loco/coding-style.md +247 -0
- package/framework/stacks/loco/error-handling.md +225 -0
- package/framework/stacks/loco/feedback.md +35 -0
- package/framework/stacks/loco/loco.md +342 -0
- package/framework/stacks/loco/structure.md +193 -0
- package/framework/stacks/loco/tech.md +129 -0
- package/framework/stacks/loco/testing.md +211 -0
- package/framework/stacks/rust/code-quality.md +370 -0
- package/framework/stacks/rust/coding-style.md +475 -0
- package/framework/stacks/rust/conventions.md +430 -0
- package/framework/stacks/rust/error-handling.md +399 -0
- package/framework/stacks/rust/feedback.md +152 -0
- package/framework/stacks/rust/memory-safety.md +398 -0
- package/framework/stacks/rust/tech.md +121 -0
- package/framework/stacks/rust/testing.md +528 -0
- package/package.json +14 -2
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
# Testing Patterns
|
|
2
|
+
|
|
3
|
+
Comprehensive testing patterns for Rust projects with `cargo-nextest`, `mockall`, `rstest`, and `proptest`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Philosophy
|
|
8
|
+
|
|
9
|
+
- **Fast feedback**: Unit tests run in milliseconds, no I/O
|
|
10
|
+
- **Compile-time confidence**: The type system catches most bugs; tests cover the rest
|
|
11
|
+
- **Readable tests**: Arrange-Act-Assert structure, descriptive names
|
|
12
|
+
- **Test at the right level**: Unit tests for logic, integration tests for boundaries
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Test Organization
|
|
17
|
+
|
|
18
|
+
### Project Layout
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
src/
|
|
22
|
+
lib.rs
|
|
23
|
+
models/
|
|
24
|
+
user.rs # Contains #[cfg(test)] mod tests at bottom
|
|
25
|
+
post.rs
|
|
26
|
+
services/
|
|
27
|
+
user_service.rs # Contains #[cfg(test)] mod tests at bottom
|
|
28
|
+
tests/
|
|
29
|
+
common/
|
|
30
|
+
mod.rs # Shared test helpers and fixtures
|
|
31
|
+
api/
|
|
32
|
+
users_test.rs # Integration tests for user API
|
|
33
|
+
posts_test.rs
|
|
34
|
+
db/
|
|
35
|
+
user_repo_test.rs
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Pattern**: Unit tests live in the same file as the code. Integration tests live in `tests/`.
|
|
39
|
+
|
|
40
|
+
### Unit Tests (Same File)
|
|
41
|
+
|
|
42
|
+
```rust
|
|
43
|
+
// src/services/user_service.rs
|
|
44
|
+
|
|
45
|
+
pub fn validate_email(email: &str) -> bool {
|
|
46
|
+
email.contains('@') && email.contains('.')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pub fn normalize_email(email: &str) -> String {
|
|
50
|
+
email.trim().to_lowercase()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#[cfg(test)]
|
|
54
|
+
mod tests {
|
|
55
|
+
use super::*;
|
|
56
|
+
|
|
57
|
+
#[test]
|
|
58
|
+
fn test_validate_email_valid() {
|
|
59
|
+
assert!(validate_email("user@example.com"));
|
|
60
|
+
assert!(validate_email("user@sub.example.com"));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#[test]
|
|
64
|
+
fn test_validate_email_invalid() {
|
|
65
|
+
assert!(!validate_email("invalid"));
|
|
66
|
+
assert!(!validate_email(""));
|
|
67
|
+
assert!(!validate_email("@example.com"));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[test]
|
|
71
|
+
fn test_normalize_email() {
|
|
72
|
+
assert_eq!(normalize_email(" User@Example.COM "), "user@example.com");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Integration Tests (`tests/` Directory)
|
|
78
|
+
|
|
79
|
+
```rust
|
|
80
|
+
// tests/api/users_test.rs
|
|
81
|
+
use axum::http::StatusCode;
|
|
82
|
+
use axum_test::TestServer;
|
|
83
|
+
use serde_json::json;
|
|
84
|
+
|
|
85
|
+
#[tokio::test]
|
|
86
|
+
async fn test_create_user_success() {
|
|
87
|
+
let server = spawn_test_server().await;
|
|
88
|
+
|
|
89
|
+
let response = server
|
|
90
|
+
.post("/api/v1/users")
|
|
91
|
+
.json(&json!({
|
|
92
|
+
"email": "new@example.com",
|
|
93
|
+
"name": "New User",
|
|
94
|
+
"password": "secure123"
|
|
95
|
+
}))
|
|
96
|
+
.await;
|
|
97
|
+
|
|
98
|
+
assert_eq!(response.status_code(), StatusCode::CREATED);
|
|
99
|
+
let body: serde_json::Value = response.json();
|
|
100
|
+
assert_eq!(body["email"], "new@example.com");
|
|
101
|
+
assert!(body.get("password").is_none());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#[tokio::test]
|
|
105
|
+
async fn test_create_user_duplicate_email_returns_409() {
|
|
106
|
+
let server = spawn_test_server().await;
|
|
107
|
+
seed_user(&server, "taken@example.com").await;
|
|
108
|
+
|
|
109
|
+
let response = server
|
|
110
|
+
.post("/api/v1/users")
|
|
111
|
+
.json(&json!({
|
|
112
|
+
"email": "taken@example.com",
|
|
113
|
+
"name": "Duplicate",
|
|
114
|
+
"password": "secure123"
|
|
115
|
+
}))
|
|
116
|
+
.await;
|
|
117
|
+
|
|
118
|
+
assert_eq!(response.status_code(), StatusCode::CONFLICT);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Async Tests with `tokio::test`
|
|
125
|
+
|
|
126
|
+
```rust
|
|
127
|
+
// GOOD: Async test with tokio
|
|
128
|
+
#[tokio::test]
|
|
129
|
+
async fn test_fetch_user_from_database() {
|
|
130
|
+
let pool = setup_test_db().await;
|
|
131
|
+
let user = create_test_user(&pool).await;
|
|
132
|
+
|
|
133
|
+
let found = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", user.id)
|
|
134
|
+
.fetch_optional(&pool)
|
|
135
|
+
.await
|
|
136
|
+
.unwrap();
|
|
137
|
+
|
|
138
|
+
assert!(found.is_some());
|
|
139
|
+
assert_eq!(found.unwrap().email, user.email);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// With custom runtime configuration
|
|
143
|
+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
144
|
+
async fn test_concurrent_operations() {
|
|
145
|
+
// ...
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Assertions
|
|
152
|
+
|
|
153
|
+
### Standard Assertions
|
|
154
|
+
|
|
155
|
+
```rust
|
|
156
|
+
#[test]
|
|
157
|
+
fn test_assertions() {
|
|
158
|
+
// Basic assertions
|
|
159
|
+
assert!(result.is_ok());
|
|
160
|
+
assert_eq!(user.name, "Alice");
|
|
161
|
+
assert_ne!(user.id, 0);
|
|
162
|
+
|
|
163
|
+
// With custom messages
|
|
164
|
+
assert!(
|
|
165
|
+
user.is_active,
|
|
166
|
+
"Expected user {} to be active, but was inactive",
|
|
167
|
+
user.id
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Pattern matching with assert_matches (nightly or use matches! macro)
|
|
171
|
+
assert!(matches!(result, Ok(User { is_active: true, .. })));
|
|
172
|
+
assert!(matches!(error, AppError::NotFound { .. }));
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Testing Errors
|
|
177
|
+
|
|
178
|
+
```rust
|
|
179
|
+
#[test]
|
|
180
|
+
fn test_invalid_email_returns_error() {
|
|
181
|
+
let result = Email::new("invalid");
|
|
182
|
+
assert!(result.is_err());
|
|
183
|
+
|
|
184
|
+
let err = result.unwrap_err();
|
|
185
|
+
assert!(err.to_string().contains("Invalid email"));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
#[tokio::test]
|
|
189
|
+
async fn test_get_missing_user_returns_not_found() {
|
|
190
|
+
let pool = setup_test_db().await;
|
|
191
|
+
let service = UserService::new(pool);
|
|
192
|
+
|
|
193
|
+
let result = service.get_user(99999).await;
|
|
194
|
+
|
|
195
|
+
assert!(matches!(result, Err(AppError::NotFound { .. })));
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Fixtures with `rstest`
|
|
202
|
+
|
|
203
|
+
### Basic Fixtures
|
|
204
|
+
|
|
205
|
+
```rust
|
|
206
|
+
use rstest::rstest;
|
|
207
|
+
|
|
208
|
+
#[rstest]
|
|
209
|
+
fn test_validate_email(
|
|
210
|
+
#[values("user@example.com", "admin@test.org", "a@b.co")]
|
|
211
|
+
valid_email: &str,
|
|
212
|
+
) {
|
|
213
|
+
assert!(validate_email(valid_email));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#[rstest]
|
|
217
|
+
fn test_reject_invalid_email(
|
|
218
|
+
#[values("", "invalid", "@example.com", "user@", "user @example.com")]
|
|
219
|
+
invalid_email: &str,
|
|
220
|
+
) {
|
|
221
|
+
assert!(!validate_email(invalid_email));
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Shared Fixtures
|
|
226
|
+
|
|
227
|
+
```rust
|
|
228
|
+
use rstest::*;
|
|
229
|
+
|
|
230
|
+
#[fixture]
|
|
231
|
+
fn test_user() -> User {
|
|
232
|
+
User {
|
|
233
|
+
id: 1,
|
|
234
|
+
email: "test@example.com".to_string(),
|
|
235
|
+
name: "Test User".to_string(),
|
|
236
|
+
is_active: true,
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
#[fixture]
|
|
241
|
+
async fn test_pool() -> PgPool {
|
|
242
|
+
setup_test_db().await
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
#[rstest]
|
|
246
|
+
fn test_user_display(test_user: User) {
|
|
247
|
+
assert_eq!(test_user.to_string(), "Test User <test@example.com>");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#[rstest]
|
|
251
|
+
#[tokio::test]
|
|
252
|
+
async fn test_save_user(#[future] test_pool: PgPool, test_user: User) {
|
|
253
|
+
let pool = test_pool.await;
|
|
254
|
+
let saved = save_user(&pool, &test_user).await.unwrap();
|
|
255
|
+
assert!(saved.id > 0);
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Parametrized Tests
|
|
260
|
+
|
|
261
|
+
```rust
|
|
262
|
+
#[rstest]
|
|
263
|
+
#[case("draft", true)]
|
|
264
|
+
#[case("published", false)]
|
|
265
|
+
#[case("archived", false)]
|
|
266
|
+
fn test_can_publish(#[case] status: &str, #[case] expected: bool) {
|
|
267
|
+
let post = Post { status: status.parse().unwrap(), ..Default::default() };
|
|
268
|
+
assert_eq!(post.can_publish(), expected);
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Mocking with `mockall`
|
|
275
|
+
|
|
276
|
+
### Trait-Based Mocking
|
|
277
|
+
|
|
278
|
+
```rust
|
|
279
|
+
use mockall::automock;
|
|
280
|
+
|
|
281
|
+
#[automock]
|
|
282
|
+
#[async_trait::async_trait]
|
|
283
|
+
pub trait UserRepository {
|
|
284
|
+
async fn get(&self, id: i64) -> Result<Option<User>, sqlx::Error>;
|
|
285
|
+
async fn get_by_email(&self, email: &str) -> Result<Option<User>, sqlx::Error>;
|
|
286
|
+
async fn save(&self, user: &NewUser) -> Result<User, sqlx::Error>;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
#[tokio::test]
|
|
290
|
+
async fn test_create_user_success() {
|
|
291
|
+
// Arrange
|
|
292
|
+
let mut mock_repo = MockUserRepository::new();
|
|
293
|
+
mock_repo
|
|
294
|
+
.expect_get_by_email()
|
|
295
|
+
.with(mockall::predicate::eq("new@example.com"))
|
|
296
|
+
.returning(|_| Ok(None));
|
|
297
|
+
mock_repo
|
|
298
|
+
.expect_save()
|
|
299
|
+
.returning(|new_user| Ok(User {
|
|
300
|
+
id: 1,
|
|
301
|
+
email: new_user.email.clone(),
|
|
302
|
+
name: new_user.name.clone(),
|
|
303
|
+
is_active: true,
|
|
304
|
+
}));
|
|
305
|
+
|
|
306
|
+
let service = UserService::new(Box::new(mock_repo));
|
|
307
|
+
|
|
308
|
+
// Act
|
|
309
|
+
let result = service.create_user(&CreateUserRequest {
|
|
310
|
+
email: "new@example.com".to_string(),
|
|
311
|
+
name: "New User".to_string(),
|
|
312
|
+
password: "secure123".to_string(),
|
|
313
|
+
}).await;
|
|
314
|
+
|
|
315
|
+
// Assert
|
|
316
|
+
assert!(result.is_ok());
|
|
317
|
+
assert_eq!(result.unwrap().email, "new@example.com");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
#[tokio::test]
|
|
321
|
+
async fn test_create_user_duplicate_email() {
|
|
322
|
+
let mut mock_repo = MockUserRepository::new();
|
|
323
|
+
mock_repo
|
|
324
|
+
.expect_get_by_email()
|
|
325
|
+
.returning(|_| Ok(Some(User {
|
|
326
|
+
id: 1,
|
|
327
|
+
email: "taken@example.com".to_string(),
|
|
328
|
+
name: "Existing".to_string(),
|
|
329
|
+
is_active: true,
|
|
330
|
+
})));
|
|
331
|
+
|
|
332
|
+
let service = UserService::new(Box::new(mock_repo));
|
|
333
|
+
|
|
334
|
+
let result = service.create_user(&CreateUserRequest {
|
|
335
|
+
email: "taken@example.com".to_string(),
|
|
336
|
+
name: "Duplicate".to_string(),
|
|
337
|
+
password: "secure123".to_string(),
|
|
338
|
+
}).await;
|
|
339
|
+
|
|
340
|
+
assert!(matches!(result, Err(AppError::Conflict(_))));
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Property Testing with `proptest`
|
|
347
|
+
|
|
348
|
+
### Basic Property Tests
|
|
349
|
+
|
|
350
|
+
```rust
|
|
351
|
+
use proptest::prelude::*;
|
|
352
|
+
|
|
353
|
+
proptest! {
|
|
354
|
+
#[test]
|
|
355
|
+
fn test_normalize_email_always_lowercase(email in "[a-zA-Z0-9]+@[a-zA-Z]+\\.[a-zA-Z]+") {
|
|
356
|
+
let normalized = normalize_email(&email);
|
|
357
|
+
assert_eq!(normalized, normalized.to_lowercase());
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
#[test]
|
|
361
|
+
fn test_parse_and_serialize_roundtrip(id in 1i64..=i64::MAX) {
|
|
362
|
+
let user_id = UserId(id);
|
|
363
|
+
let serialized = serde_json::to_string(&user_id).unwrap();
|
|
364
|
+
let deserialized: UserId = serde_json::from_str(&serialized).unwrap();
|
|
365
|
+
assert_eq!(user_id.0, deserialized.0);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
#[test]
|
|
369
|
+
fn test_pagination_invariants(page in 1u32..=1000, per_page in 1u32..=100) {
|
|
370
|
+
let offset = calculate_offset(page, per_page);
|
|
371
|
+
assert!(offset < page as u64 * per_page as u64);
|
|
372
|
+
assert_eq!(offset, ((page - 1) as u64) * (per_page as u64));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Custom Strategies
|
|
378
|
+
|
|
379
|
+
```rust
|
|
380
|
+
use proptest::prelude::*;
|
|
381
|
+
|
|
382
|
+
fn valid_email_strategy() -> impl Strategy<Value = String> {
|
|
383
|
+
("[a-z]{1,10}", "[a-z]{1,10}", "[a-z]{2,4}")
|
|
384
|
+
.prop_map(|(user, domain, tld)| format!("{user}@{domain}.{tld}"))
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
proptest! {
|
|
388
|
+
#[test]
|
|
389
|
+
fn test_email_validation_accepts_valid(email in valid_email_strategy()) {
|
|
390
|
+
assert!(validate_email(&email));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Test Execution with `cargo-nextest`
|
|
398
|
+
|
|
399
|
+
### Running Tests
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
# Install
|
|
403
|
+
cargo install cargo-nextest
|
|
404
|
+
|
|
405
|
+
# Run all tests
|
|
406
|
+
cargo nextest run
|
|
407
|
+
|
|
408
|
+
# Run with output
|
|
409
|
+
cargo nextest run --no-capture
|
|
410
|
+
|
|
411
|
+
# Run specific test
|
|
412
|
+
cargo nextest run test_create_user
|
|
413
|
+
|
|
414
|
+
# Run tests in specific module
|
|
415
|
+
cargo nextest run --filter-expr 'test(user_service)'
|
|
416
|
+
|
|
417
|
+
# Run with retries for flaky tests
|
|
418
|
+
cargo nextest run --retries 2
|
|
419
|
+
|
|
420
|
+
# Run only integration tests
|
|
421
|
+
cargo nextest run --filter-expr 'kind(test)'
|
|
422
|
+
|
|
423
|
+
# Run with specific number of threads
|
|
424
|
+
cargo nextest run -j 4
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### `.config/nextest.toml`
|
|
428
|
+
|
|
429
|
+
```toml
|
|
430
|
+
[profile.default]
|
|
431
|
+
retries = 0
|
|
432
|
+
slow-timeout = { period = "30s", terminate-after = 2 }
|
|
433
|
+
fail-fast = true
|
|
434
|
+
|
|
435
|
+
[profile.ci]
|
|
436
|
+
retries = 2
|
|
437
|
+
fail-fast = false
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Coverage with `cargo-llvm-cov`
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
# Install
|
|
446
|
+
cargo install cargo-llvm-cov
|
|
447
|
+
|
|
448
|
+
# Run with coverage
|
|
449
|
+
cargo llvm-cov
|
|
450
|
+
|
|
451
|
+
# Generate HTML report
|
|
452
|
+
cargo llvm-cov --html
|
|
453
|
+
open target/llvm-cov/html/index.html
|
|
454
|
+
|
|
455
|
+
# Fail if coverage is below threshold
|
|
456
|
+
cargo llvm-cov --fail-under-lines 80
|
|
457
|
+
|
|
458
|
+
# Generate LCOV for CI integration
|
|
459
|
+
cargo llvm-cov --lcov --output-path lcov.info
|
|
460
|
+
|
|
461
|
+
# Coverage for nextest
|
|
462
|
+
cargo llvm-cov nextest
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## Test Markers and Attributes
|
|
468
|
+
|
|
469
|
+
```rust
|
|
470
|
+
// Ignore slow tests by default
|
|
471
|
+
#[test]
|
|
472
|
+
#[ignore]
|
|
473
|
+
fn test_full_pipeline() {
|
|
474
|
+
// Run with: cargo test -- --ignored
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Conditional compilation
|
|
478
|
+
#[cfg(feature = "integration-tests")]
|
|
479
|
+
#[tokio::test]
|
|
480
|
+
async fn test_real_database() {
|
|
481
|
+
// Run with: cargo test --features integration-tests
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Should panic
|
|
485
|
+
#[test]
|
|
486
|
+
#[should_panic(expected = "index out of bounds")]
|
|
487
|
+
fn test_index_out_of_bounds() {
|
|
488
|
+
let v: Vec<i32> = vec![];
|
|
489
|
+
let _ = v[0];
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## Test Commands Summary
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
# Fast feedback
|
|
499
|
+
cargo nextest run -j auto # All tests, parallel
|
|
500
|
+
cargo nextest run --filter-expr 'test(user)' # Pattern match
|
|
501
|
+
cargo test --doc # Doctests only
|
|
502
|
+
|
|
503
|
+
# Full suite
|
|
504
|
+
cargo nextest run --all-features # All features enabled
|
|
505
|
+
cargo llvm-cov nextest --fail-under-lines 80 # With coverage gate
|
|
506
|
+
|
|
507
|
+
# Specific targets
|
|
508
|
+
cargo nextest run -p my-crate # Single crate in workspace
|
|
509
|
+
cargo nextest run --filter-expr 'kind(test)' # Integration tests only
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## Anti-Patterns
|
|
515
|
+
|
|
516
|
+
| Anti-Pattern | Problem | Correct Approach |
|
|
517
|
+
|---|---|---|
|
|
518
|
+
| `#[test] fn test1()` | Non-descriptive name | `fn test_create_user_with_duplicate_email_returns_conflict()` |
|
|
519
|
+
| Testing implementation details | Brittle, breaks on refactor | Test behavior and public API |
|
|
520
|
+
| No async test runtime | `async fn` tests silently do nothing | Use `#[tokio::test]` |
|
|
521
|
+
| Shared mutable state between tests | Flaky, order-dependent | Each test sets up its own state |
|
|
522
|
+
| `.unwrap()` without context in tests | Confusing failure messages | Use `.expect("reason")` |
|
|
523
|
+
| Giant test functions | Hard to identify failure | One assertion per logical concept |
|
|
524
|
+
| Mocking everything | Tests prove nothing | Mock boundaries, test logic directly |
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
_Tests document behavior. Each test should read as a specification of what the code does._
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "red64-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Red64 Flow Orchestrator - Deterministic spec-driven development CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,8 +24,20 @@
|
|
|
24
24
|
"workflow",
|
|
25
25
|
"orchestration"
|
|
26
26
|
],
|
|
27
|
-
"author":
|
|
27
|
+
"author": {
|
|
28
|
+
"name": "Yacin Bahi",
|
|
29
|
+
"email": "yacin@red64.io",
|
|
30
|
+
"url": "https://red64.io"
|
|
31
|
+
},
|
|
28
32
|
"license": "MIT",
|
|
33
|
+
"homepage": "https://github.com/Red64llc/red64-cli#readme",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/Red64llc/red64-cli.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/Red64llc/red64-cli/issues"
|
|
40
|
+
},
|
|
29
41
|
"dependencies": {
|
|
30
42
|
"@inkjs/ui": "^2.0.0",
|
|
31
43
|
"date-fns": "^3.6.0",
|