1use anyhow::Result;
2use depyler_annotations::{LambdaAnnotations, LambdaEventType};
3use serde_json::{json, Value};
6use std::collections::HashMap;
7#[derive(Debug, Clone)]
12pub struct LambdaTestHarness {
13 test_events: HashMap<LambdaEventType, Vec<TestEvent>>,
14 test_context: TestContext,
15 performance_benchmarks: PerformanceBenchmarks,
16}
17
18#[derive(Debug, Clone)]
19pub struct TestEvent {
20 pub name: String,
21 pub event_data: Value,
22 pub expected_response: Option<Value>,
23 pub should_succeed: bool,
24 pub description: String,
25}
26
27#[derive(Debug, Clone)]
28pub struct TestContext {
29 pub function_name: String,
30 pub function_version: String,
31 pub memory_limit_mb: u32,
32 pub timeout_ms: u64,
33 pub aws_request_id: String,
34 pub invoked_function_arn: String,
35}
36
37#[derive(Debug, Clone)]
38pub struct PerformanceBenchmarks {
39 pub max_cold_start_ms: u64,
40 pub max_warm_start_ms: u64,
41 pub max_memory_usage_mb: u32,
42 pub min_throughput_rps: u32,
43}
44
45#[derive(Debug, Clone)]
46pub struct TestResult {
47 pub test_name: String,
48 pub success: bool,
49 pub duration_ms: u64,
50 pub memory_usage_mb: Option<u32>,
51 pub error_message: Option<String>,
52 pub response: Option<Value>,
53}
54
55#[derive(Debug, Clone)]
56pub struct BenchmarkResult {
57 pub cold_start_ms: u64,
58 pub warm_start_ms: u64,
59 pub memory_usage_mb: u32,
60 pub throughput_rps: f64,
61 pub binary_size_kb: u32,
62}
63
64impl Default for TestContext {
65 fn default() -> Self {
66 Self {
67 function_name: "test-function".to_string(),
68 function_version: "$LATEST".to_string(),
69 memory_limit_mb: 128,
70 timeout_ms: 15000,
71 aws_request_id: "test-request-id".to_string(),
72 invoked_function_arn: "arn:aws:lambda:us-east-1:123456789012:function:test-function"
73 .to_string(),
74 }
75 }
76}
77
78impl Default for PerformanceBenchmarks {
79 fn default() -> Self {
80 Self {
81 max_cold_start_ms: 100,
82 max_warm_start_ms: 10,
83 max_memory_usage_mb: 64,
84 min_throughput_rps: 100,
85 }
86 }
87}
88
89impl Default for LambdaTestHarness {
90 fn default() -> Self {
91 Self::new()
92 }
93}
94
95impl LambdaTestHarness {
96 pub fn new() -> Self {
97 let mut test_events = HashMap::new();
98
99 test_events.insert(
101 LambdaEventType::ApiGatewayProxyRequest,
102 vec![
103 TestEvent {
104 name: "basic_get_request".to_string(),
105 event_data: json!({
106 "httpMethod": "GET",
107 "path": "/test",
108 "headers": {
109 "Content-Type": "application/json"
110 },
111 "queryStringParameters": {
112 "param1": "value1"
113 },
114 "body": null,
115 "pathParameters": {
116 "id": "123"
117 },
118 "requestContext": {
119 "requestId": "test-request-id",
120 "stage": "test"
121 }
122 }),
123 expected_response: Some(json!({
124 "statusCode": 200,
125 "headers": {
126 "Content-Type": "application/json"
127 },
128 "body": "{\"message\":\"success\"}"
129 })),
130 should_succeed: true,
131 description: "Basic GET request test".to_string(),
132 },
133 TestEvent {
134 name: "post_request_with_body".to_string(),
135 event_data: json!({
136 "httpMethod": "POST",
137 "path": "/test",
138 "headers": {
139 "Content-Type": "application/json"
140 },
141 "body": "{\"name\":\"test\",\"value\":42}",
142 "requestContext": {
143 "requestId": "test-request-id-2"
144 }
145 }),
146 expected_response: None,
147 should_succeed: true,
148 description: "POST request with JSON body".to_string(),
149 },
150 ],
151 );
152
153 test_events.insert(
154 LambdaEventType::S3Event,
155 vec![TestEvent {
156 name: "s3_object_created".to_string(),
157 event_data: json!({
158 "Records": [
159 {
160 "s3": {
161 "bucket": {
162 "name": "test-bucket"
163 },
164 "object": {
165 "key": "test-file.txt",
166 "size": 1024
167 }
168 },
169 "eventName": "ObjectCreated:Put"
170 }
171 ]
172 }),
173 expected_response: None,
174 should_succeed: true,
175 description: "S3 object created event".to_string(),
176 }],
177 );
178
179 test_events.insert(
180 LambdaEventType::SqsEvent,
181 vec![
182 TestEvent {
183 name: "sqs_single_message".to_string(),
184 event_data: json!({
185 "Records": [
186 {
187 "messageId": "test-message-id",
188 "receiptHandle": "test-receipt-handle",
189 "body": "{\"message\":\"Hello World\"}",
190 "attributes": {
191 "ApproximateReceiveCount": "1"
192 }
193 }
194 ]
195 }),
196 expected_response: Some(json!({
197 "batchItemFailures": []
198 })),
199 should_succeed: true,
200 description: "Single SQS message processing".to_string(),
201 },
202 TestEvent {
203 name: "sqs_batch_messages".to_string(),
204 event_data: json!({
205 "Records": [
206 {
207 "messageId": "msg1",
208 "body": "{\"id\":1}"
209 },
210 {
211 "messageId": "msg2",
212 "body": "{\"id\":2}"
213 },
214 {
215 "messageId": "msg3",
216 "body": "invalid json"
217 }
218 ]
219 }),
220 expected_response: Some(json!({
221 "batchItemFailures": [
222 {
223 "itemIdentifier": "msg3"
224 }
225 ]
226 })),
227 should_succeed: true,
228 description: "Batch SQS messages with one failure".to_string(),
229 },
230 ],
231 );
232
233 Self {
234 test_events,
235 test_context: TestContext::default(),
236 performance_benchmarks: PerformanceBenchmarks::default(),
237 }
238 }
239
240 pub fn with_context(mut self, context: TestContext) -> Self {
241 self.test_context = context;
242 self
243 }
244
245 pub fn with_benchmarks(mut self, benchmarks: PerformanceBenchmarks) -> Self {
246 self.performance_benchmarks = benchmarks;
247 self
248 }
249
250 pub fn add_test_event(&mut self, event_type: LambdaEventType, test_event: TestEvent) {
252 self.test_events
253 .entry(event_type)
254 .or_default()
255 .push(test_event);
256 }
257
258 pub fn generate_test_suite(&self, annotations: &LambdaAnnotations) -> Result<String> {
260 let mut test_code = String::new();
261
262 test_code.push_str(&self.generate_test_imports());
263 test_code.push_str(&self.generate_test_helpers());
264
265 if let Some(ref event_type) = annotations.event_type {
266 if let Some(events) = self.test_events.get(event_type) {
267 for event in events {
268 test_code.push_str(&self.generate_individual_test(event, event_type)?);
269 }
270 }
271 }
272
273 test_code.push_str(&self.generate_performance_tests());
274 test_code.push_str(&self.generate_integration_tests());
275
276 Ok(test_code)
277 }
278
279 fn generate_test_imports(&self) -> String {
280 r#"#[cfg(test)]
281mod tests {{
282 use super::*;
283 use lambda_runtime::{{Context, LambdaEvent}};
284 use serde_json::{{json, Value}};
285 use std::time::Instant;
286 // use tokio::time::timeout;
287
288"#
289 .to_string()
290 }
291
292 fn generate_test_helpers(&self) -> String {
293 format!(
294 r#" // Test helper functions
295 fn create_test_context() -> Context {{
296 Context {{
297 request_id: "{}".to_string(),
298 deadline: std::time::Instant::now() + std::time::Duration::from_millis({}),
299 invoked_function_arn: "{}".to_string(),
300 xray_trace_id: None,
301 client_context: None,
302 identity: None,
303 env_config: lambda_runtime::Config::default(),
304 }}
305 }}
306
307 async fn run_with_timeout<F, T>(
308 future: F,
309 timeout_ms: u64,
310 ) -> Result<T, Box<dyn std::error::Error>>
311 where
312 F: std::future::Future<Output = Result<T, Box<dyn std::error::Error>>>,
313 {{
314 match timeout(std::time::Duration::from_millis(timeout_ms), future).await {{
315 Ok(result) => result,
316 Err(_) => Err(format!("Test timed out after {{}}ms", timeout_ms).into()),
317 }}
318 }}
319
320"#,
321 self.test_context.aws_request_id,
322 self.test_context.timeout_ms,
323 self.test_context.invoked_function_arn
324 )
325 }
326
327 fn generate_individual_test(
328 &self,
329 test_event: &TestEvent,
330 _event_type: &LambdaEventType,
331 ) -> Result<String> {
332 let test_function_name = format!("test_{}", test_event.name);
333 let handler_name = "handler"; let mut test_code = format!(
336 r#" #[tokio::test]
337 async fn {}() {{
338 // {}
339 let event_data = {};
340 let context = create_test_context();
341 let event = LambdaEvent::new(event_data, context);
342
343 let start_time = Instant::now();
344 let result = run_with_timeout(
345 async {{ {}(event).await.map_err(|e| Box::new(e) as Box<dyn std::error::Error>) }},
346 {}
347 ).await;
348 let duration = start_time.elapsed();
349
350 println!("Test '{}' completed in {{:?}}", duration);
351
352"#,
353 test_function_name,
354 test_event.description,
355 serde_json::to_string_pretty(&test_event.event_data)?,
356 handler_name,
357 self.test_context.timeout_ms,
358 test_event.name
359 );
360
361 if test_event.should_succeed {
362 test_code.push_str(" assert!(result.is_ok(), \"Test should succeed but failed: {:?}\", result.err());\n");
363
364 if let Some(ref expected) = test_event.expected_response {
365 test_code.push_str(&format!(
366 r#"
367 let response = result.unwrap();
368 let expected_response = {};
369 assert_eq!(response, expected_response, "Response doesn't match expected");
370"#,
371 serde_json::to_string_pretty(expected)?
372 ));
373 }
374 } else {
375 test_code.push_str(
376 " assert!(result.is_err(), \"Test should fail but succeeded\");\n",
377 );
378 }
379
380 test_code.push_str(&format!(
382 r#"
383 // Performance assertions
384 assert!(duration.as_millis() < {}, "Test took too long: {{:?}}", duration);
385"#,
386 self.performance_benchmarks.max_warm_start_ms
387 ));
388
389 test_code.push_str(" }\n\n");
390 Ok(test_code)
391 }
392
393 fn generate_performance_tests(&self) -> String {
394 format!(
395 r#" #[tokio::test]
396 async fn test_cold_start_performance() {{
397 // Simulate cold start by running handler for the first time
398 let event_data = json!({{ "test": "cold_start" }});
399 let context = create_test_context();
400 let event = LambdaEvent::new(event_data, context);
401
402 let start_time = Instant::now();
403 let _result = handler(event).await;
404 let cold_start_duration = start_time.elapsed();
405
406 println!("Cold start duration: {{:?}}", cold_start_duration);
407 assert!(
408 cold_start_duration.as_millis() < {},
409 "Cold start took too long: {{:?}}",
410 cold_start_duration
411 );
412 }}
413
414 #[tokio::test]
415 async fn test_warm_start_performance() {{
416 // First invocation (warm up)
417 let event_data = json!({{ "test": "warm_up" }});
418 let context = create_test_context();
419 let event = LambdaEvent::new(event_data, context);
420 let _ = handler(event).await;
421
422 // Second invocation (warm start)
423 let event_data = json!({{ "test": "warm_start" }});
424 let context = create_test_context();
425 let event = LambdaEvent::new(event_data, context);
426
427 let start_time = Instant::now();
428 let _result = handler(event).await;
429 let warm_start_duration = start_time.elapsed();
430
431 println!("Warm start duration: {{:?}}", warm_start_duration);
432 assert!(
433 warm_start_duration.as_millis() < {},
434 "Warm start took too long: {{:?}}",
435 warm_start_duration
436 );
437 }}
438
439 #[tokio::test]
440 async fn test_memory_usage() {{
441 // This is a placeholder - actual memory testing would require
442 // system-specific tools or profiling crates
443 println!("Memory usage test - implement with system profiling tools");
444
445 // You could use crates like `memory-stats` or system calls here
446 // For now, we'll just ensure the test runs
447 let event_data = json!({{ "test": "memory" }});
448 let context = create_test_context();
449 let event = LambdaEvent::new(event_data, context);
450
451 let _result = handler(event).await;
452 // Memory assertions would go here
453 }}
454
455 #[tokio::test]
456 async fn test_concurrent_invocations() {{
457 use std::sync::Arc;
458 use tokio::sync::Semaphore;
459
460 let concurrency = 10;
461 let semaphore = Arc::new(Semaphore::new(concurrency));
462 let mut handles = Vec::new();
463
464 let start_time = Instant::now();
465
466 for i in 0..concurrency {{
467 let permit = semaphore.clone().acquire_owned().await.unwrap();
468 let handle = tokio::spawn(async move {{
469 let _permit = permit;
470 let event_data = json!({{ "test": "concurrent", "id": i }});
471 let context = create_test_context();
472 let event = LambdaEvent::new(event_data, context);
473 handler(event).await
474 }});
475 handles.push(handle);
476 }}
477
478 // Wait for all invocations to complete
479 for handle in handles {{
480 let result = handle.await.unwrap();
481 assert!(result.is_ok(), "Concurrent invocation failed: {{:?}}", result.err());
482 }}
483
484 let total_duration = start_time.elapsed();
485 let throughput = concurrency as f64 / total_duration.as_secs_f64();
486
487 println!("Throughput: {{:.2}} RPS", throughput);
488 assert!(
489 throughput >= {} as f64,
490 "Throughput too low: {{:.2}} RPS",
491 throughput
492 );
493 }}
494
495"#,
496 self.performance_benchmarks.max_cold_start_ms,
497 self.performance_benchmarks.max_warm_start_ms,
498 self.performance_benchmarks.min_throughput_rps
499 )
500 }
501
502 fn generate_integration_tests(&self) -> String {
503 r#" #[tokio::test]
504 async fn test_error_handling() {{
505 // Test with invalid event data to ensure proper error handling
506 let invalid_event = json!({{ "invalid": "data" }});
507 let context = create_test_context();
508 let event = LambdaEvent::new(invalid_event, context);
509
510 let result = handler(event).await;
511
512 // Depending on your error handling strategy, adjust this assertion
513 // For now, we'll just ensure it doesn't panic
514 println!("Error handling result: {{:?}}", result);
515 }}
516
517 #[tokio::test]
518 async fn test_timeout_handling() {{
519 // Test timeout behavior (if applicable)
520 let event_data = json!({{ "test": "timeout" }});
521 let context = create_test_context();
522 let event = LambdaEvent::new(event_data, context);
523
524 // Set a very short timeout to test timeout handling
525 let result = run_with_timeout(
526 async {{ handler(event).await.map_err(|e| Box::new(e) as Box<dyn std::error::Error>) }},
527 1 // 1ms timeout
528 ).await;
529
530 // This should timeout
531 assert!(result.is_err(), "Handler should have timed out");
532 }}
533
534 #[tokio::test]
535 async fn test_large_payload() {{
536 // Test with a large payload to ensure memory efficiency
537 let large_data = "x".repeat(1024 * 100); // 100KB string
538 let event_data = json!({{ "large_data": large_data }});
539 let context = create_test_context();
540 let event = LambdaEvent::new(event_data, context);
541
542 let start_time = Instant::now();
543 let result = handler(event).await;
544 let duration = start_time.elapsed();
545
546 assert!(result.is_ok(), "Large payload test failed: {{:?}}", result.err());
547 println!("Large payload processing time: {{:?}}", duration);
548 }}
549}}
550"#
551 .to_string()
552 }
553
554 pub fn generate_cargo_lambda_test_script(
556 &self,
557 annotations: &LambdaAnnotations,
558 ) -> Result<String> {
559 let mut script = String::from("#!/bin/bash\n");
560 script.push_str("# Generated test script for cargo-lambda\n\n");
561 script.push_str("set -e\n\n");
562
563 script.push_str("echo \"Building Lambda function...\"\n");
565 script.push_str("cargo lambda build --release\n\n");
566
567 script.push_str("echo \"Running unit tests...\"\n");
569 script.push_str("cargo test\n\n");
570
571 script.push_str("echo \"Running integration tests with cargo-lambda...\"\n");
573
574 if let Some(ref event_type) = annotations.event_type {
575 if let Some(events) = self.test_events.get(event_type) {
576 for event in events {
577 script.push_str(&format!("echo \"Testing event: {}\"\n", event.name));
578
579 script.push_str(&format!(
581 "cat > /tmp/test_event_{}.json << 'EOF'\n{}\nEOF\n",
582 event.name,
583 serde_json::to_string_pretty(&event.event_data)?
584 ));
585
586 script.push_str(&format!(
588 "cargo lambda invoke --data-file /tmp/test_event_{}.json\n",
589 event.name
590 ));
591
592 script.push_str(&format!("rm /tmp/test_event_{}.json\n\n", event.name));
593 }
594 }
595 }
596
597 script.push_str("echo \"Running performance benchmarks...\"\n");
599 script.push_str("if command -v hyperfine > /dev/null; then\n");
600 script.push_str(" echo \"Benchmarking cold start performance...\"\n");
601 script.push_str(
602 " hyperfine 'cargo lambda invoke --data-ascii \\'{}\\'' --warmup 1 --min-runs 10\n",
603 );
604 script.push_str("else\n");
605 script.push_str(" echo \"hyperfine not installed, skipping performance benchmarks\"\n");
606 script.push_str("fi\n\n");
607
608 script.push_str("echo \"All tests completed successfully!\"\n");
609
610 Ok(script)
611 }
612
613 pub fn generate_github_actions_workflow(
615 &self,
616 annotations: &LambdaAnnotations,
617 ) -> Result<String> {
618 Ok(format!(
619 r#"name: Lambda Function Tests
620
621on:
622 push:
623 branches: [ main, develop ]
624 pull_request:
625 branches: [ main ]
626
627env:
628 CARGO_TERM_COLOR: always
629
630jobs:
631 test:
632 runs-on: ubuntu-latest
633
634 steps:
635 - uses: actions/checkout@v3
636
637 - name: Install Rust
638 uses: actions-rs/toolchain@v1
639 with:
640 toolchain: stable
641 override: true
642 components: rustfmt, clippy
643
644 - name: Install cargo-lambda
645 run: |
646 pip install cargo-lambda
647
648 - name: Cache cargo registry
649 uses: actions/cache@v3
650 with:
651 path: ~/.cargo/registry
652 key: ${{{{ runner.os }}}}-cargo-registry-${{{{ hashFiles('**/Cargo.lock') }}}}
653
654 - name: Cache cargo index
655 uses: actions/cache@v3
656 with:
657 path: ~/.cargo/git
658 key: ${{{{ runner.os }}}}-cargo-index-${{{{ hashFiles('**/Cargo.lock') }}}}
659
660 - name: Cache cargo build
661 uses: actions/cache@v3
662 with:
663 path: target
664 key: ${{{{ runner.os }}}}-cargo-build-target-${{{{ hashFiles('**/Cargo.lock') }}}}
665
666 - name: Format check
667 run: cargo fmt --check
668
669 - name: Clippy check
670 run: cargo clippy -- -D warnings
671
672 - name: Run tests
673 run: cargo test --verbose
674
675 - name: Build Lambda function
676 run: cargo lambda build --release{}
677
678 - name: Test Lambda function locally
679 run: |
680 # Create test events
681{}
682
683 # Run integration tests
684 cargo lambda invoke --data-file test_events/basic_test.json
685
686 - name: Performance benchmarks
687 if: github.event_name == 'push'
688 run: |
689 if command -v hyperfine > /dev/null; then
690 hyperfine 'cargo lambda invoke --data-ascii "{{\"test\":\"benchmark\"}}"' --warmup 1
691 fi
692
693 - name: Security audit
694 run: |
695 cargo install --force cargo-audit
696 cargo audit
697
698 - name: Check binary size
699 run: |
700 BINARY_SIZE=$(du -h target/lambda/*/bootstrap | cut -f1)
701 echo "Binary size: $BINARY_SIZE"
702 # Add size threshold check here if needed
703"#,
704 match annotations.architecture {
705 depyler_annotations::Architecture::Arm64 => " --arm64",
706 depyler_annotations::Architecture::X86_64 => " --x86-64",
707 },
708 self.generate_test_events_yaml(annotations)?
709 ))
710 }
711
712 fn generate_test_events_yaml(&self, annotations: &LambdaAnnotations) -> Result<String> {
713 let mut yaml = String::from(" mkdir -p test_events\n");
714
715 if let Some(ref event_type) = annotations.event_type {
716 if let Some(events) = self.test_events.get(event_type) {
717 for (i, event) in events.iter().enumerate() {
718 yaml.push_str(&format!(
719 " cat > test_events/test_{}.json << 'EOF'\n{}\n EOF\n",
720 i,
721 serde_json::to_string_pretty(&event.event_data)?
722 ));
723 }
724 }
725 }
726
727 yaml.push_str(" cat > test_events/basic_test.json << 'EOF'\n{\"test\": \"basic\"}\n EOF\n");
728
729 Ok(yaml)
730 }
731
732 pub fn generate_local_dev_script(&self) -> String {
734 r#"#!/bin/bash
735# Local development testing script
736
737set -e
738
739echo "🚀 Starting local Lambda development testing..."
740
741# Build the function
742echo "📦 Building Lambda function..."
743cargo lambda build --release
744
745# Run unit tests
746echo "🧪 Running unit tests..."
747cargo test
748
749# Start local development server (if available)
750if command -v cargo-lambda &> /dev/null; then
751 echo "🌐 Starting local Lambda server..."
752 echo "You can test your function at: http://localhost:9000/lambda-url/function_name/"
753
754 # Start the server in background
755 cargo lambda start &
756 SERVER_PID=$!
757
758 # Wait a moment for server to start
759 sleep 2
760
761 # Test basic invocation
762 echo "🔍 Testing basic invocation..."
763 curl -X POST \
764 -H "Content-Type: application/json" \
765 -d '{{"test": "local_development"}}' \
766 http://localhost:9000/2015-03-31/functions/function/invocations
767
768 echo -e "\n✅ Local testing completed!"
769 echo "🛑 Stopping local server..."
770 kill $SERVER_PID
771else
772 echo "⚠️ cargo-lambda not found. Install with: pip install cargo-lambda"
773fi
774
775echo "🎉 Development testing finished!"
776"#
777 .to_string()
778 }
779
780 pub fn generate_load_test_script(&self, annotations: &LambdaAnnotations) -> Result<String> {
782 Ok(format!(
783 r#"#!/bin/bash
784# Load testing script for Lambda function
785
786set -e
787
788echo "🔥 Starting load testing..."
789
790# Configuration
791CONCURRENCY=10
792DURATION=30
793MEMORY_SIZE={}
794
795# Build first
796cargo lambda build --release
797
798# Create test event
799cat > load_test_event.json << 'EOF'
800{{"test": "load_test", "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"}}
801EOF
802
803# Run load test
804if command -v hey > /dev/null; then
805 echo "Running load test with hey..."
806
807 # Start local server
808 cargo lambda start &
809 SERVER_PID=$!
810 sleep 2
811
812 # Run load test
813 hey -z ${{DURATION}}s -c ${{CONCURRENCY}} \
814 -m POST \
815 -H "Content-Type: application/json" \
816 -D load_test_event.json \
817 http://localhost:9000/2015-03-31/functions/function/invocations
818
819 # Cleanup
820 kill $SERVER_PID
821 rm load_test_event.json
822
823elif command -v ab > /dev/null; then
824 echo "Running load test with Apache Bench..."
825
826 # Start local server
827 cargo lambda start &
828 SERVER_PID=$!
829 sleep 2
830
831 # Run ab test
832 ab -n 1000 -c ${{CONCURRENCY}} \
833 -p load_test_event.json \
834 -T application/json \
835 http://localhost:9000/2015-03-31/functions/function/invocations
836
837 # Cleanup
838 kill $SERVER_PID
839 rm load_test_event.json
840
841else
842 echo "No load testing tool found. Install 'hey' or 'ab' (Apache Bench)"
843 exit 1
844fi
845
846echo "✅ Load testing completed!"
847"#,
848 annotations.memory_size
849 ))
850 }
851}
852
853impl TestContext {
854 }
867
868#[cfg(test)]
869mod tests {
870 use super::*;
871
872 #[test]
875 fn test_test_event_fields() {
876 let event = TestEvent {
877 name: "my_test".to_string(),
878 event_data: json!({"key": "value"}),
879 expected_response: Some(json!({"status": "ok"})),
880 should_succeed: true,
881 description: "My test description".to_string(),
882 };
883 assert_eq!(event.name, "my_test");
884 assert_eq!(event.event_data["key"], "value");
885 assert!(event.expected_response.is_some());
886 assert!(event.should_succeed);
887 assert_eq!(event.description, "My test description");
888 }
889
890 #[test]
891 fn test_test_event_no_expected_response() {
892 let event = TestEvent {
893 name: "test".to_string(),
894 event_data: json!({}),
895 expected_response: None,
896 should_succeed: false,
897 description: String::new(),
898 };
899 assert!(event.expected_response.is_none());
900 assert!(!event.should_succeed);
901 }
902
903 #[test]
904 fn test_test_event_clone() {
905 let event = TestEvent {
906 name: "clone_test".to_string(),
907 event_data: json!({"a": 1}),
908 expected_response: None,
909 should_succeed: true,
910 description: "desc".to_string(),
911 };
912 let cloned = event.clone();
913 assert_eq!(cloned.name, event.name);
914 assert_eq!(cloned.event_data, event.event_data);
915 }
916
917 #[test]
918 fn test_test_event_debug() {
919 let event = TestEvent {
920 name: "debug_test".to_string(),
921 event_data: json!({}),
922 expected_response: None,
923 should_succeed: true,
924 description: String::new(),
925 };
926 let debug = format!("{:?}", event);
927 assert!(debug.contains("TestEvent"));
928 assert!(debug.contains("debug_test"));
929 }
930
931 #[test]
934 fn test_test_context_default() {
935 let ctx = TestContext::default();
936 assert_eq!(ctx.function_name, "test-function");
937 assert_eq!(ctx.function_version, "$LATEST");
938 assert_eq!(ctx.memory_limit_mb, 128);
939 assert_eq!(ctx.timeout_ms, 15000);
940 assert_eq!(ctx.aws_request_id, "test-request-id");
941 assert!(ctx.invoked_function_arn.contains("arn:aws:lambda"));
942 }
943
944 #[test]
945 fn test_test_context_custom() {
946 let ctx = TestContext {
947 function_name: "custom-func".to_string(),
948 function_version: "v1".to_string(),
949 memory_limit_mb: 256,
950 timeout_ms: 30000,
951 aws_request_id: "custom-id".to_string(),
952 invoked_function_arn: "custom-arn".to_string(),
953 };
954 assert_eq!(ctx.function_name, "custom-func");
955 assert_eq!(ctx.memory_limit_mb, 256);
956 }
957
958 #[test]
959 fn test_test_context_clone() {
960 let ctx = TestContext::default();
961 let cloned = ctx.clone();
962 assert_eq!(cloned.function_name, ctx.function_name);
963 assert_eq!(cloned.timeout_ms, ctx.timeout_ms);
964 }
965
966 #[test]
967 fn test_test_context_debug() {
968 let ctx = TestContext::default();
969 let debug = format!("{:?}", ctx);
970 assert!(debug.contains("TestContext"));
971 assert!(debug.contains("test-function"));
972 }
973
974 #[test]
977 fn test_performance_benchmarks_default() {
978 let bench = PerformanceBenchmarks::default();
979 assert_eq!(bench.max_cold_start_ms, 100);
980 assert_eq!(bench.max_warm_start_ms, 10);
981 assert_eq!(bench.max_memory_usage_mb, 64);
982 assert_eq!(bench.min_throughput_rps, 100);
983 }
984
985 #[test]
986 fn test_performance_benchmarks_clone() {
987 let bench = PerformanceBenchmarks {
988 max_cold_start_ms: 50,
989 max_warm_start_ms: 5,
990 max_memory_usage_mb: 32,
991 min_throughput_rps: 200,
992 };
993 let cloned = bench.clone();
994 assert_eq!(cloned.max_cold_start_ms, bench.max_cold_start_ms);
995 }
996
997 #[test]
998 fn test_performance_benchmarks_debug() {
999 let bench = PerformanceBenchmarks::default();
1000 let debug = format!("{:?}", bench);
1001 assert!(debug.contains("PerformanceBenchmarks"));
1002 }
1003
1004 #[test]
1007 fn test_test_result_success() {
1008 let result = TestResult {
1009 test_name: "success_test".to_string(),
1010 success: true,
1011 duration_ms: 100,
1012 memory_usage_mb: Some(32),
1013 error_message: None,
1014 response: Some(json!({"result": "ok"})),
1015 };
1016 assert!(result.success);
1017 assert_eq!(result.duration_ms, 100);
1018 assert!(result.error_message.is_none());
1019 }
1020
1021 #[test]
1022 fn test_test_result_failure() {
1023 let result = TestResult {
1024 test_name: "failure_test".to_string(),
1025 success: false,
1026 duration_ms: 50,
1027 memory_usage_mb: None,
1028 error_message: Some("Test failed".to_string()),
1029 response: None,
1030 };
1031 assert!(!result.success);
1032 assert!(result.error_message.is_some());
1033 assert!(result.memory_usage_mb.is_none());
1034 }
1035
1036 #[test]
1037 fn test_test_result_clone() {
1038 let result = TestResult {
1039 test_name: "test".to_string(),
1040 success: true,
1041 duration_ms: 10,
1042 memory_usage_mb: None,
1043 error_message: None,
1044 response: None,
1045 };
1046 let cloned = result.clone();
1047 assert_eq!(cloned.test_name, result.test_name);
1048 }
1049
1050 #[test]
1051 fn test_test_result_debug() {
1052 let result = TestResult {
1053 test_name: "debug_test".to_string(),
1054 success: true,
1055 duration_ms: 0,
1056 memory_usage_mb: None,
1057 error_message: None,
1058 response: None,
1059 };
1060 let debug = format!("{:?}", result);
1061 assert!(debug.contains("TestResult"));
1062 }
1063
1064 #[test]
1067 fn test_benchmark_result_fields() {
1068 let result = BenchmarkResult {
1069 cold_start_ms: 50,
1070 warm_start_ms: 5,
1071 memory_usage_mb: 32,
1072 throughput_rps: 150.5,
1073 binary_size_kb: 1024,
1074 };
1075 assert_eq!(result.cold_start_ms, 50);
1076 assert_eq!(result.warm_start_ms, 5);
1077 assert_eq!(result.memory_usage_mb, 32);
1078 assert!((result.throughput_rps - 150.5).abs() < f64::EPSILON);
1079 assert_eq!(result.binary_size_kb, 1024);
1080 }
1081
1082 #[test]
1083 fn test_benchmark_result_clone() {
1084 let result = BenchmarkResult {
1085 cold_start_ms: 100,
1086 warm_start_ms: 10,
1087 memory_usage_mb: 64,
1088 throughput_rps: 100.0,
1089 binary_size_kb: 512,
1090 };
1091 let cloned = result.clone();
1092 assert_eq!(cloned.cold_start_ms, result.cold_start_ms);
1093 }
1094
1095 #[test]
1096 fn test_benchmark_result_debug() {
1097 let result = BenchmarkResult {
1098 cold_start_ms: 0,
1099 warm_start_ms: 0,
1100 memory_usage_mb: 0,
1101 throughput_rps: 0.0,
1102 binary_size_kb: 0,
1103 };
1104 let debug = format!("{:?}", result);
1105 assert!(debug.contains("BenchmarkResult"));
1106 }
1107
1108 #[test]
1111 fn test_harness_creation() {
1112 let harness = LambdaTestHarness::new();
1113 assert!(!harness.test_events.is_empty());
1114 assert!(harness
1115 .test_events
1116 .contains_key(&LambdaEventType::ApiGatewayProxyRequest));
1117 assert!(harness.test_events.contains_key(&LambdaEventType::S3Event));
1118 assert!(harness.test_events.contains_key(&LambdaEventType::SqsEvent));
1119 }
1120
1121 #[test]
1122 fn test_harness_default() {
1123 let harness = LambdaTestHarness::default();
1124 assert!(!harness.test_events.is_empty());
1125 }
1126
1127 #[test]
1128 fn test_harness_clone() {
1129 let harness = LambdaTestHarness::new();
1130 let cloned = harness.clone();
1131 assert_eq!(cloned.test_events.len(), harness.test_events.len());
1132 }
1133
1134 #[test]
1135 fn test_harness_debug() {
1136 let harness = LambdaTestHarness::new();
1137 let debug = format!("{:?}", harness);
1138 assert!(debug.contains("LambdaTestHarness"));
1139 }
1140
1141 #[test]
1142 fn test_harness_with_context() {
1143 let ctx = TestContext {
1144 function_name: "custom-func".to_string(),
1145 ..TestContext::default()
1146 };
1147 let harness = LambdaTestHarness::new().with_context(ctx);
1148 assert_eq!(harness.test_context.function_name, "custom-func");
1149 }
1150
1151 #[test]
1152 fn test_custom_test_event() {
1153 let mut harness = LambdaTestHarness::new();
1154
1155 let custom_event = TestEvent {
1156 name: "custom_test".to_string(),
1157 event_data: json!({"custom": "data"}),
1158 expected_response: None,
1159 should_succeed: true,
1160 description: "Custom test event".to_string(),
1161 };
1162
1163 harness.add_test_event(LambdaEventType::ApiGatewayProxyRequest, custom_event);
1164
1165 let events = harness
1166 .test_events
1167 .get(&LambdaEventType::ApiGatewayProxyRequest)
1168 .unwrap();
1169 assert!(events.iter().any(|e| e.name == "custom_test"));
1170 }
1171
1172 #[test]
1173 fn test_add_test_event_new_type() {
1174 let mut harness = LambdaTestHarness::new();
1175
1176 let event = TestEvent {
1177 name: "sns_test".to_string(),
1178 event_data: json!({"Records": []}),
1179 expected_response: None,
1180 should_succeed: true,
1181 description: "SNS test".to_string(),
1182 };
1183
1184 harness.add_test_event(LambdaEventType::SnsEvent, event);
1185 assert!(harness.test_events.contains_key(&LambdaEventType::SnsEvent));
1186 }
1187
1188 #[test]
1189 fn test_test_suite_generation() {
1190 let harness = LambdaTestHarness::new();
1191 let annotations = depyler_annotations::LambdaAnnotations {
1192 event_type: Some(LambdaEventType::ApiGatewayProxyRequest),
1193 ..Default::default()
1194 };
1195
1196 let test_suite = harness.generate_test_suite(&annotations).unwrap();
1197
1198 assert!(test_suite.contains("#[tokio::test]"));
1199 assert!(test_suite.contains("test_basic_get_request"));
1200 assert!(test_suite.contains("test_cold_start_performance"));
1201 }
1202
1203 #[test]
1204 fn test_test_suite_no_event_type() {
1205 let harness = LambdaTestHarness::new();
1206 let annotations = depyler_annotations::LambdaAnnotations::default();
1207
1208 let test_suite = harness.generate_test_suite(&annotations).unwrap();
1209
1210 assert!(test_suite.contains("test_cold_start_performance"));
1212 assert!(test_suite.contains("test_error_handling"));
1213 }
1214
1215 #[test]
1216 fn test_test_suite_sqs_event() {
1217 let harness = LambdaTestHarness::new();
1218 let annotations = depyler_annotations::LambdaAnnotations {
1219 event_type: Some(LambdaEventType::SqsEvent),
1220 ..Default::default()
1221 };
1222
1223 let test_suite = harness.generate_test_suite(&annotations).unwrap();
1224
1225 assert!(test_suite.contains("test_sqs_single_message"));
1226 assert!(test_suite.contains("test_sqs_batch_messages"));
1227 }
1228
1229 #[test]
1230 fn test_test_suite_s3_event() {
1231 let harness = LambdaTestHarness::new();
1232 let annotations = depyler_annotations::LambdaAnnotations {
1233 event_type: Some(LambdaEventType::S3Event),
1234 ..Default::default()
1235 };
1236
1237 let test_suite = harness.generate_test_suite(&annotations).unwrap();
1238
1239 assert!(test_suite.contains("test_s3_object_created"));
1240 }
1241
1242 #[test]
1243 fn test_generate_test_imports() {
1244 let harness = LambdaTestHarness::new();
1245 let imports = harness.generate_test_imports();
1246
1247 assert!(imports.contains("#[cfg(test)]"));
1248 assert!(imports.contains("mod tests"));
1249 assert!(imports.contains("use super::*"));
1250 assert!(imports.contains("serde_json"));
1251 }
1252
1253 #[test]
1254 fn test_generate_test_helpers() {
1255 let harness = LambdaTestHarness::new();
1256 let helpers = harness.generate_test_helpers();
1257
1258 assert!(helpers.contains("create_test_context"));
1259 assert!(helpers.contains("run_with_timeout"));
1260 assert!(helpers.contains("test-request-id"));
1261 }
1262
1263 #[test]
1264 fn test_generate_performance_tests() {
1265 let harness = LambdaTestHarness::new();
1266 let perf_tests = harness.generate_performance_tests();
1267
1268 assert!(perf_tests.contains("test_cold_start_performance"));
1269 assert!(perf_tests.contains("test_warm_start_performance"));
1270 assert!(perf_tests.contains("test_memory_usage"));
1271 assert!(perf_tests.contains("test_concurrent_invocations"));
1272 }
1273
1274 #[test]
1275 fn test_generate_integration_tests() {
1276 let harness = LambdaTestHarness::new();
1277 let integration_tests = harness.generate_integration_tests();
1278
1279 assert!(integration_tests.contains("test_error_handling"));
1280 assert!(integration_tests.contains("test_timeout_handling"));
1281 assert!(integration_tests.contains("test_large_payload"));
1282 }
1283
1284 #[test]
1285 fn test_github_actions_workflow() {
1286 let harness = LambdaTestHarness::new();
1287 let annotations = depyler_annotations::LambdaAnnotations::default();
1288
1289 let workflow = harness
1290 .generate_github_actions_workflow(&annotations)
1291 .unwrap();
1292
1293 assert!(workflow.contains("name: Lambda Function Tests"));
1294 assert!(workflow.contains("cargo lambda build"));
1295 assert!(workflow.contains("cargo test"));
1296 }
1297
1298 #[test]
1299 fn test_github_actions_workflow_arm64() {
1300 let harness = LambdaTestHarness::new();
1301 let annotations = depyler_annotations::LambdaAnnotations {
1302 architecture: depyler_annotations::Architecture::Arm64,
1303 ..Default::default()
1304 };
1305
1306 let workflow = harness
1307 .generate_github_actions_workflow(&annotations)
1308 .unwrap();
1309
1310 assert!(workflow.contains("--arm64"));
1311 }
1312
1313 #[test]
1314 fn test_github_actions_workflow_x86() {
1315 let harness = LambdaTestHarness::new();
1316 let annotations = depyler_annotations::LambdaAnnotations {
1317 architecture: depyler_annotations::Architecture::X86_64,
1318 ..Default::default()
1319 };
1320
1321 let workflow = harness
1322 .generate_github_actions_workflow(&annotations)
1323 .unwrap();
1324
1325 assert!(workflow.contains("--x86-64"));
1326 }
1327
1328 #[test]
1329 fn test_cargo_lambda_script() {
1330 let harness = LambdaTestHarness::new();
1331 let annotations = depyler_annotations::LambdaAnnotations {
1332 event_type: Some(LambdaEventType::S3Event),
1333 ..Default::default()
1334 };
1335
1336 let script = harness
1337 .generate_cargo_lambda_test_script(&annotations)
1338 .unwrap();
1339
1340 assert!(script.contains("cargo lambda build"));
1341 assert!(script.contains("cargo lambda invoke"));
1342 assert!(script.contains("s3_object_created"));
1343 }
1344
1345 #[test]
1346 fn test_cargo_lambda_script_no_event_type() {
1347 let harness = LambdaTestHarness::new();
1348 let annotations = depyler_annotations::LambdaAnnotations::default();
1349
1350 let script = harness
1351 .generate_cargo_lambda_test_script(&annotations)
1352 .unwrap();
1353
1354 assert!(script.contains("cargo lambda build"));
1355 assert!(script.contains("cargo test"));
1356 }
1357
1358 #[test]
1359 fn test_local_dev_script() {
1360 let harness = LambdaTestHarness::new();
1361 let script = harness.generate_local_dev_script();
1362
1363 assert!(script.contains("#!/bin/bash"));
1364 assert!(script.contains("cargo lambda build"));
1365 assert!(script.contains("cargo lambda start"));
1366 assert!(script.contains("curl"));
1367 }
1368
1369 #[test]
1370 fn test_load_test_script() {
1371 let harness = LambdaTestHarness::new();
1372 let annotations = depyler_annotations::LambdaAnnotations {
1373 memory_size: 256,
1374 ..Default::default()
1375 };
1376
1377 let script = harness.generate_load_test_script(&annotations).unwrap();
1378
1379 assert!(script.contains("#!/bin/bash"));
1380 assert!(script.contains("MEMORY_SIZE=256"));
1381 assert!(script.contains("hey") || script.contains("ab"));
1382 }
1383
1384 #[test]
1385 fn test_performance_benchmarks_configuration() {
1386 let benchmarks = PerformanceBenchmarks {
1387 max_cold_start_ms: 50,
1388 max_warm_start_ms: 5,
1389 max_memory_usage_mb: 32,
1390 min_throughput_rps: 200,
1391 };
1392
1393 let harness = LambdaTestHarness::new().with_benchmarks(benchmarks);
1394
1395 assert_eq!(harness.performance_benchmarks.max_cold_start_ms, 50);
1396 assert_eq!(harness.performance_benchmarks.min_throughput_rps, 200);
1397 }
1398
1399 #[test]
1400 fn test_context_conversion() {
1401 let test_context = TestContext {
1402 function_name: "test-func".to_string(),
1403 aws_request_id: "test-123".to_string(),
1404 timeout_ms: 5000,
1405 ..TestContext::default()
1406 };
1407
1408 assert_eq!(test_context.aws_request_id, "test-123");
1410 }
1411
1412 #[test]
1413 fn test_generate_test_events_yaml() {
1414 let harness = LambdaTestHarness::new();
1415 let annotations = depyler_annotations::LambdaAnnotations {
1416 event_type: Some(LambdaEventType::ApiGatewayProxyRequest),
1417 ..Default::default()
1418 };
1419
1420 let yaml = harness.generate_test_events_yaml(&annotations).unwrap();
1421
1422 assert!(yaml.contains("mkdir -p test_events"));
1423 assert!(yaml.contains("test_events/"));
1424 assert!(yaml.contains("basic_test.json"));
1425 }
1426
1427 #[test]
1428 fn test_generate_test_events_yaml_no_event_type() {
1429 let harness = LambdaTestHarness::new();
1430 let annotations = depyler_annotations::LambdaAnnotations::default();
1431
1432 let yaml = harness.generate_test_events_yaml(&annotations).unwrap();
1433
1434 assert!(yaml.contains("basic_test.json"));
1436 }
1437
1438 #[test]
1439 fn test_individual_test_generation_should_fail() {
1440 let harness = LambdaTestHarness::new();
1441 let event = TestEvent {
1442 name: "fail_test".to_string(),
1443 event_data: json!({"test": "data"}),
1444 expected_response: None,
1445 should_succeed: false,
1446 description: "Should fail test".to_string(),
1447 };
1448
1449 let test_code = harness
1450 .generate_individual_test(&event, &LambdaEventType::ApiGatewayProxyRequest)
1451 .unwrap();
1452
1453 assert!(test_code.contains("test_fail_test"));
1454 assert!(test_code.contains("should fail but succeeded"));
1455 }
1456
1457 #[test]
1458 fn test_individual_test_generation_with_expected() {
1459 let harness = LambdaTestHarness::new();
1460 let event = TestEvent {
1461 name: "expected_test".to_string(),
1462 event_data: json!({"input": 1}),
1463 expected_response: Some(json!({"output": 2})),
1464 should_succeed: true,
1465 description: "With expected response".to_string(),
1466 };
1467
1468 let test_code = harness
1469 .generate_individual_test(&event, &LambdaEventType::ApiGatewayProxyRequest)
1470 .unwrap();
1471
1472 assert!(test_code.contains("expected_response"));
1473 assert!(test_code.contains("assert_eq"));
1474 }
1475
1476 #[test]
1477 fn test_api_gateway_default_events() {
1478 let harness = LambdaTestHarness::new();
1479 let events = harness
1480 .test_events
1481 .get(&LambdaEventType::ApiGatewayProxyRequest)
1482 .unwrap();
1483
1484 assert!(events.iter().any(|e| e.name == "basic_get_request"));
1485 assert!(events.iter().any(|e| e.name == "post_request_with_body"));
1486 }
1487
1488 #[test]
1489 fn test_sqs_default_events() {
1490 let harness = LambdaTestHarness::new();
1491 let events = harness.test_events.get(&LambdaEventType::SqsEvent).unwrap();
1492
1493 assert!(events.iter().any(|e| e.name == "sqs_single_message"));
1494 assert!(events.iter().any(|e| e.name == "sqs_batch_messages"));
1495 }
1496
1497 #[test]
1498 fn test_s3_default_events() {
1499 let harness = LambdaTestHarness::new();
1500 let events = harness.test_events.get(&LambdaEventType::S3Event).unwrap();
1501
1502 assert!(events.iter().any(|e| e.name == "s3_object_created"));
1503 }
1504}