Skip to main content

depyler_lambda/
lambda_testing.rs

1use anyhow::Result;
2use depyler_annotations::{LambdaAnnotations, LambdaEventType};
3// use lambda_runtime::{Context, LambdaEvent};
4// use serde::{Deserialize, Serialize};
5use serde_json::{json, Value};
6use std::collections::HashMap;
7// use std::time::Duration;
8// use tokio::time::timeout;
9
10/// Local Lambda testing harness for development and CI/CD
11#[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        // Add default test events for common Lambda event types
100        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    /// Add a custom test event
251    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    /// Generate test suite for a Lambda function
259    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"; // This could be configurable
334
335        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        // Add performance assertions
381        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    /// Generate a complete test script for cargo lambda test
555    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        // Build first
564        script.push_str("echo \"Building Lambda function...\"\n");
565        script.push_str("cargo lambda build --release\n\n");
566
567        // Run unit tests
568        script.push_str("echo \"Running unit tests...\"\n");
569        script.push_str("cargo test\n\n");
570
571        // Run integration tests with cargo-lambda
572        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                    // Create temporary event file
580                    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                    // Invoke with cargo lambda
587                    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        // Performance benchmarks
598        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    /// Generate a GitHub Actions workflow for Lambda testing
614    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    /// Generate local development testing script
733    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    /// Generate load testing script
781    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    // Note: This would be available when using the actual lambda_runtime crate
855    // pub fn to_lambda_context(&self) -> Context {
856    //     Context {
857    //         request_id: self.aws_request_id.clone(),
858    //         deadline: std::time::Instant::now() + Duration::from_millis(self.timeout_ms),
859    //         invoked_function_arn: self.invoked_function_arn.clone(),
860    //         xray_trace_id: None,
861    //         client_context: None,
862    //         identity: None,
863    //         env_config: lambda_runtime::Config::default(),
864    //     }
865    // }
866}
867
868#[cfg(test)]
869mod tests {
870    use super::*;
871
872    // === TestEvent tests ===
873
874    #[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    // === TestContext tests ===
932
933    #[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    // === PerformanceBenchmarks tests ===
975
976    #[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    // === TestResult tests ===
1005
1006    #[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    // === BenchmarkResult tests ===
1065
1066    #[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    // === LambdaTestHarness tests ===
1109
1110    #[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        // Should still generate performance and integration tests
1211        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        // Note: Context conversion would be available when using the actual lambda_runtime crate
1409        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        // Should still create basic test event
1435        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}