1use std::sync::Arc;
2
3use async_trait::async_trait;
4use chrono::Utc;
5use http::{Method, StatusCode};
6use serde_json::{json, Value};
7use sha2::{Digest, Sha256};
8
9use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
10
11use crate::runtime::ContainerRuntime;
12use crate::state::{EventSourceMapping, LambdaFunction, SharedLambdaState};
13
14pub struct LambdaService {
15 state: SharedLambdaState,
16 runtime: Option<Arc<ContainerRuntime>>,
17}
18
19impl LambdaService {
20 pub fn new(state: SharedLambdaState) -> Self {
21 Self {
22 state,
23 runtime: None,
24 }
25 }
26
27 pub fn with_runtime(mut self, runtime: Arc<ContainerRuntime>) -> Self {
28 self.runtime = Some(runtime);
29 self
30 }
31
32 fn resolve_action(req: &AwsRequest) -> Option<(&str, Option<String>)> {
45 let segs = &req.path_segments;
46 if segs.is_empty() {
47 return None;
48 }
49
50 if segs[0] != "2015-03-31" {
52 return None;
53 }
54
55 match (req.method.clone(), segs.len()) {
56 (Method::POST, 2) if segs[1] == "functions" => Some(("CreateFunction", None)),
58 (Method::GET, 2) if segs[1] == "functions" => Some(("ListFunctions", None)),
59 (Method::GET, 3) if segs[1] == "functions" => {
61 Some(("GetFunction", Some(segs[2].clone())))
62 }
63 (Method::DELETE, 3) if segs[1] == "functions" => {
64 Some(("DeleteFunction", Some(segs[2].clone())))
65 }
66 (Method::POST, 4) if segs[1] == "functions" && segs[3] == "invocations" => {
68 Some(("Invoke", Some(segs[2].clone())))
69 }
70 (Method::POST, 4) if segs[1] == "functions" && segs[3] == "versions" => {
72 Some(("PublishVersion", Some(segs[2].clone())))
73 }
74 (Method::POST, 2) if segs[1] == "event-source-mappings" => {
76 Some(("CreateEventSourceMapping", None))
77 }
78 (Method::GET, 2) if segs[1] == "event-source-mappings" => {
79 Some(("ListEventSourceMappings", None))
80 }
81 (Method::GET, 3) if segs[1] == "event-source-mappings" => {
83 Some(("GetEventSourceMapping", Some(segs[2].clone())))
84 }
85 (Method::DELETE, 3) if segs[1] == "event-source-mappings" => {
86 Some(("DeleteEventSourceMapping", Some(segs[2].clone())))
87 }
88 _ => None,
89 }
90 }
91
92 fn create_function(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
93 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
94 let function_name = body["FunctionName"]
95 .as_str()
96 .ok_or_else(|| {
97 AwsServiceError::aws_error(
98 StatusCode::BAD_REQUEST,
99 "InvalidParameterValueException",
100 "FunctionName is required",
101 )
102 })?
103 .to_string();
104
105 let mut state = self.state.write();
106
107 if state.functions.contains_key(&function_name) {
108 return Err(AwsServiceError::aws_error(
109 StatusCode::CONFLICT,
110 "ResourceConflictException",
111 format!("Function already exist: {}", function_name),
112 ));
113 }
114
115 let runtime = body["Runtime"].as_str().unwrap_or("python3.12").to_string();
116 let role = body["Role"].as_str().unwrap_or("").to_string();
117 let handler = body["Handler"]
118 .as_str()
119 .unwrap_or("index.handler")
120 .to_string();
121 let description = body["Description"].as_str().unwrap_or("").to_string();
122 let timeout = body["Timeout"].as_i64().unwrap_or(3);
123 let memory_size = body["MemorySize"].as_i64().unwrap_or(128);
124 let package_type = body["PackageType"].as_str().unwrap_or("Zip").to_string();
125
126 let tags: std::collections::HashMap<String, String> = body["Tags"]
127 .as_object()
128 .map(|m| {
129 m.iter()
130 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
131 .collect()
132 })
133 .unwrap_or_default();
134
135 let environment: std::collections::HashMap<String, String> = body["Environment"]
136 ["Variables"]
137 .as_object()
138 .map(|m| {
139 m.iter()
140 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
141 .collect()
142 })
143 .unwrap_or_default();
144
145 let architectures = body["Architectures"]
146 .as_array()
147 .map(|a| {
148 a.iter()
149 .filter_map(|v| v.as_str().map(|s| s.to_string()))
150 .collect()
151 })
152 .unwrap_or_else(|| vec!["x86_64".to_string()]);
153
154 let code_zip: Option<Vec<u8>> = match body["Code"]["ZipFile"].as_str() {
156 Some(b64) => Some(
157 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, b64).map_err(
158 |_| {
159 AwsServiceError::aws_error(
160 StatusCode::BAD_REQUEST,
161 "InvalidParameterValueException",
162 "Could not decode Code.ZipFile: invalid base64",
163 )
164 },
165 )?,
166 ),
167 None => None,
168 };
169
170 let code_fallback = serde_json::to_vec(&body["Code"]).unwrap_or_default();
172 let code_bytes = code_zip.as_deref().unwrap_or(&code_fallback);
173 let mut hasher = Sha256::new();
174 hasher.update(code_bytes);
175 let hash = hasher.finalize();
176 let code_sha256 = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, hash);
177 let code_size = code_bytes.len() as i64;
178
179 let function_arn = format!(
180 "arn:aws:lambda:{}:{}:function:{}",
181 state.region, state.account_id, function_name
182 );
183 let now = Utc::now();
184
185 let func = LambdaFunction {
186 function_name: function_name.clone(),
187 function_arn: function_arn.clone(),
188 runtime: runtime.clone(),
189 role: role.clone(),
190 handler: handler.clone(),
191 description: description.clone(),
192 timeout,
193 memory_size,
194 code_sha256: code_sha256.clone(),
195 code_size,
196 version: "$LATEST".to_string(),
197 last_modified: now,
198 tags,
199 environment: environment.clone(),
200 architectures: architectures.clone(),
201 package_type: package_type.clone(),
202 code_zip,
203 };
204
205 let response = self.function_config_json(&func);
206
207 state.functions.insert(function_name, func);
208
209 Ok(AwsResponse::json(StatusCode::CREATED, response.to_string()))
210 }
211
212 fn get_function(&self, function_name: &str) -> Result<AwsResponse, AwsServiceError> {
213 let state = self.state.read();
214 let func = state.functions.get(function_name).ok_or_else(|| {
215 AwsServiceError::aws_error(
216 StatusCode::NOT_FOUND,
217 "ResourceNotFoundException",
218 format!(
219 "Function not found: arn:aws:lambda:{}:{}:function:{}",
220 state.region, state.account_id, function_name
221 ),
222 )
223 })?;
224
225 let config = self.function_config_json(func);
226 let response = json!({
227 "Code": {
228 "Location": format!("https://awslambda-{}-tasks.s3.{}.amazonaws.com/stub",
229 func.function_arn.split(':').nth(3).unwrap_or("us-east-1"),
230 func.function_arn.split(':').nth(3).unwrap_or("us-east-1")),
231 "RepositoryType": "S3"
232 },
233 "Configuration": config,
234 "Tags": func.tags,
235 });
236
237 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
238 }
239
240 fn delete_function(&self, function_name: &str) -> Result<AwsResponse, AwsServiceError> {
241 let mut state = self.state.write();
242 let region = state.region.clone();
243 let account_id = state.account_id.clone();
244 if state.functions.remove(function_name).is_none() {
245 return Err(AwsServiceError::aws_error(
246 StatusCode::NOT_FOUND,
247 "ResourceNotFoundException",
248 format!(
249 "Function not found: arn:aws:lambda:{}:{}:function:{}",
250 region, account_id, function_name
251 ),
252 ));
253 }
254
255 if let Some(ref runtime) = self.runtime {
257 let rt = runtime.clone();
258 let name = function_name.to_string();
259 tokio::spawn(async move { rt.stop_container(&name).await });
260 }
261
262 Ok(AwsResponse::json(StatusCode::NO_CONTENT, ""))
263 }
264
265 fn list_functions(&self) -> Result<AwsResponse, AwsServiceError> {
266 let state = self.state.read();
267 let functions: Vec<Value> = state
268 .functions
269 .values()
270 .map(|f| self.function_config_json(f))
271 .collect();
272
273 let response = json!({
274 "Functions": functions,
275 });
276
277 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
278 }
279
280 async fn invoke(
281 &self,
282 function_name: &str,
283 payload: &[u8],
284 ) -> Result<AwsResponse, AwsServiceError> {
285 let func = {
286 let state = self.state.read();
287 state.functions.get(function_name).cloned().ok_or_else(|| {
288 AwsServiceError::aws_error(
289 StatusCode::NOT_FOUND,
290 "ResourceNotFoundException",
291 format!(
292 "Function not found: arn:aws:lambda:{}:{}:function:{}",
293 state.region, state.account_id, function_name
294 ),
295 )
296 })?
297 };
298
299 let runtime = self.runtime.as_ref().ok_or_else(|| {
300 AwsServiceError::aws_error(
301 StatusCode::INTERNAL_SERVER_ERROR,
302 "ServiceException",
303 "Docker/Podman is required for Lambda execution but is not available",
304 )
305 })?;
306
307 if func.code_zip.is_none() {
308 return Err(AwsServiceError::aws_error(
309 StatusCode::BAD_REQUEST,
310 "InvalidParameterValueException",
311 "Function has no deployment package",
312 ));
313 }
314
315 match runtime.invoke(&func, payload).await {
316 Ok(response_bytes) => {
317 let mut resp = AwsResponse::json(StatusCode::OK, response_bytes);
318 resp.headers.insert(
319 http::header::HeaderName::from_static("x-amz-executed-version"),
320 http::header::HeaderValue::from_static("$LATEST"),
321 );
322 Ok(resp)
323 }
324 Err(e) => {
325 tracing::error!(function = %function_name, error = %e, "Lambda invocation failed");
326 Err(AwsServiceError::aws_error(
327 StatusCode::INTERNAL_SERVER_ERROR,
328 "ServiceException",
329 format!("Lambda execution failed: {e}"),
330 ))
331 }
332 }
333 }
334
335 fn publish_version(&self, function_name: &str) -> Result<AwsResponse, AwsServiceError> {
336 let state = self.state.read();
337 let func = state.functions.get(function_name).ok_or_else(|| {
338 AwsServiceError::aws_error(
339 StatusCode::NOT_FOUND,
340 "ResourceNotFoundException",
341 format!(
342 "Function not found: arn:aws:lambda:{}:{}:function:{}",
343 state.region, state.account_id, function_name
344 ),
345 )
346 })?;
347
348 let mut config = self.function_config_json(func);
349 config["Version"] = json!("1");
351 config["FunctionArn"] = json!(format!("{}:1", func.function_arn));
352
353 Ok(AwsResponse::json(StatusCode::CREATED, config.to_string()))
354 }
355
356 fn create_event_source_mapping(
357 &self,
358 req: &AwsRequest,
359 ) -> Result<AwsResponse, AwsServiceError> {
360 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
361 let event_source_arn = body["EventSourceArn"]
362 .as_str()
363 .ok_or_else(|| {
364 AwsServiceError::aws_error(
365 StatusCode::BAD_REQUEST,
366 "InvalidParameterValueException",
367 "EventSourceArn is required",
368 )
369 })?
370 .to_string();
371
372 let function_name = body["FunctionName"]
373 .as_str()
374 .ok_or_else(|| {
375 AwsServiceError::aws_error(
376 StatusCode::BAD_REQUEST,
377 "InvalidParameterValueException",
378 "FunctionName is required",
379 )
380 })?
381 .to_string();
382
383 let mut state = self.state.write();
384
385 let function_arn = if function_name.starts_with("arn:") {
387 function_name.clone()
388 } else {
389 let func = state.functions.get(&function_name).ok_or_else(|| {
390 AwsServiceError::aws_error(
391 StatusCode::NOT_FOUND,
392 "ResourceNotFoundException",
393 format!(
394 "Function not found: arn:aws:lambda:{}:{}:function:{}",
395 state.region, state.account_id, function_name
396 ),
397 )
398 })?;
399 func.function_arn.clone()
400 };
401
402 let batch_size = body["BatchSize"].as_i64().unwrap_or(10);
403 let enabled = body["Enabled"].as_bool().unwrap_or(true);
404 let mapping_uuid = uuid::Uuid::new_v4().to_string();
405 let now = Utc::now();
406
407 let mapping = EventSourceMapping {
408 uuid: mapping_uuid.clone(),
409 function_arn: function_arn.clone(),
410 event_source_arn: event_source_arn.clone(),
411 batch_size,
412 enabled,
413 state: if enabled {
414 "Enabled".to_string()
415 } else {
416 "Disabled".to_string()
417 },
418 last_modified: now,
419 };
420
421 let response = self.event_source_mapping_json(&mapping);
422 state.event_source_mappings.insert(mapping_uuid, mapping);
423
424 Ok(AwsResponse::json(
425 StatusCode::ACCEPTED,
426 response.to_string(),
427 ))
428 }
429
430 fn list_event_source_mappings(&self) -> Result<AwsResponse, AwsServiceError> {
431 let state = self.state.read();
432 let mappings: Vec<Value> = state
433 .event_source_mappings
434 .values()
435 .map(|m| self.event_source_mapping_json(m))
436 .collect();
437
438 let response = json!({
439 "EventSourceMappings": mappings,
440 });
441
442 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
443 }
444
445 fn get_event_source_mapping(&self, uuid: &str) -> Result<AwsResponse, AwsServiceError> {
446 let state = self.state.read();
447 let mapping = state.event_source_mappings.get(uuid).ok_or_else(|| {
448 AwsServiceError::aws_error(
449 StatusCode::NOT_FOUND,
450 "ResourceNotFoundException",
451 format!("The resource you requested does not exist. (Service: Lambda, Status Code: 404, Request ID: {uuid})"),
452 )
453 })?;
454
455 let response = self.event_source_mapping_json(mapping);
456 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
457 }
458
459 fn delete_event_source_mapping(&self, uuid: &str) -> Result<AwsResponse, AwsServiceError> {
460 let mut state = self.state.write();
461 let mapping = state.event_source_mappings.remove(uuid).ok_or_else(|| {
462 AwsServiceError::aws_error(
463 StatusCode::NOT_FOUND,
464 "ResourceNotFoundException",
465 format!("The resource you requested does not exist. (Service: Lambda, Status Code: 404, Request ID: {uuid})"),
466 )
467 })?;
468
469 let mut response = self.event_source_mapping_json(&mapping);
470 response["State"] = json!("Deleting");
471 Ok(AwsResponse::json(
472 StatusCode::ACCEPTED,
473 response.to_string(),
474 ))
475 }
476
477 fn function_config_json(&self, func: &LambdaFunction) -> Value {
478 let mut env_vars = json!({});
479 if !func.environment.is_empty() {
480 env_vars = json!({ "Variables": func.environment });
481 }
482
483 json!({
484 "FunctionName": func.function_name,
485 "FunctionArn": func.function_arn,
486 "Runtime": func.runtime,
487 "Role": func.role,
488 "Handler": func.handler,
489 "Description": func.description,
490 "Timeout": func.timeout,
491 "MemorySize": func.memory_size,
492 "CodeSha256": func.code_sha256,
493 "CodeSize": func.code_size,
494 "Version": func.version,
495 "LastModified": func.last_modified.format("%Y-%m-%dT%H:%M:%S%.3f+0000").to_string(),
496 "PackageType": func.package_type,
497 "Architectures": func.architectures,
498 "Environment": env_vars,
499 "State": "Active",
500 "LastUpdateStatus": "Successful",
501 "TracingConfig": { "Mode": "PassThrough" },
502 "RevisionId": uuid::Uuid::new_v4().to_string(),
503 })
504 }
505
506 fn event_source_mapping_json(&self, mapping: &EventSourceMapping) -> Value {
507 json!({
508 "UUID": mapping.uuid,
509 "FunctionArn": mapping.function_arn,
510 "EventSourceArn": mapping.event_source_arn,
511 "BatchSize": mapping.batch_size,
512 "State": mapping.state,
513 "LastModified": mapping.last_modified.timestamp_millis() as f64 / 1000.0,
514 })
515 }
516}
517
518#[async_trait]
519impl AwsService for LambdaService {
520 fn service_name(&self) -> &str {
521 "lambda"
522 }
523
524 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
525 let (action, resource_name) = Self::resolve_action(&req).ok_or_else(|| {
526 AwsServiceError::aws_error(
527 StatusCode::NOT_FOUND,
528 "UnknownOperationException",
529 format!("Unknown operation: {} {}", req.method, req.raw_path),
530 )
531 })?;
532
533 match action {
534 "CreateFunction" => self.create_function(&req),
535 "ListFunctions" => self.list_functions(),
536 "GetFunction" => self.get_function(resource_name.as_deref().unwrap_or("")),
537 "DeleteFunction" => self.delete_function(resource_name.as_deref().unwrap_or("")),
538 "Invoke" => {
539 self.invoke(resource_name.as_deref().unwrap_or(""), &req.body)
540 .await
541 }
542 "PublishVersion" => self.publish_version(resource_name.as_deref().unwrap_or("")),
543 "CreateEventSourceMapping" => self.create_event_source_mapping(&req),
544 "ListEventSourceMappings" => self.list_event_source_mappings(),
545 "GetEventSourceMapping" => {
546 self.get_event_source_mapping(resource_name.as_deref().unwrap_or(""))
547 }
548 "DeleteEventSourceMapping" => {
549 self.delete_event_source_mapping(resource_name.as_deref().unwrap_or(""))
550 }
551 _ => Err(AwsServiceError::action_not_implemented("lambda", action)),
552 }
553 }
554
555 fn supported_actions(&self) -> &[&str] {
556 &[
557 "CreateFunction",
558 "GetFunction",
559 "DeleteFunction",
560 "ListFunctions",
561 "Invoke",
562 "PublishVersion",
563 "CreateEventSourceMapping",
564 "ListEventSourceMappings",
565 "GetEventSourceMapping",
566 "DeleteEventSourceMapping",
567 ]
568 }
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574 use crate::state::LambdaState;
575 use bytes::Bytes;
576 use http::{HeaderMap, Method};
577 use parking_lot::RwLock;
578 use std::collections::HashMap;
579 use std::sync::Arc;
580
581 fn make_state() -> SharedLambdaState {
582 Arc::new(RwLock::new(LambdaState::new("123456789012", "us-east-1")))
583 }
584
585 fn make_request(method: Method, path: &str, body: &str) -> AwsRequest {
586 let path_segments: Vec<String> = path
587 .split('/')
588 .filter(|s| !s.is_empty())
589 .map(|s| s.to_string())
590 .collect();
591 AwsRequest {
592 service: "lambda".to_string(),
593 action: String::new(),
594 region: "us-east-1".to_string(),
595 account_id: "123456789012".to_string(),
596 request_id: "test-request-id".to_string(),
597 headers: HeaderMap::new(),
598 query_params: HashMap::new(),
599 body: Bytes::from(body.to_string()),
600 path_segments,
601 raw_path: path.to_string(),
602 raw_query: String::new(),
603 method,
604 is_query_protocol: false,
605 access_key_id: None,
606 }
607 }
608
609 #[tokio::test]
610 async fn test_create_and_get_function() {
611 let state = make_state();
612 let svc = LambdaService::new(state);
613
614 let create_body = json!({
615 "FunctionName": "my-func",
616 "Runtime": "python3.12",
617 "Role": "arn:aws:iam::123456789012:role/test-role",
618 "Handler": "index.handler",
619 "Code": { "ZipFile": "UEsFBgAAAAAAAAAAAAAAAAAAAAA=" }
620 });
621
622 let req = make_request(
623 Method::POST,
624 "/2015-03-31/functions",
625 &create_body.to_string(),
626 );
627 let resp = svc.handle(req).await.unwrap();
628 assert_eq!(resp.status, StatusCode::CREATED);
629
630 let body: Value = serde_json::from_slice(&resp.body).unwrap();
631 assert_eq!(body["FunctionName"], "my-func");
632 assert_eq!(body["Runtime"], "python3.12");
633
634 let req = make_request(Method::GET, "/2015-03-31/functions/my-func", "");
636 let resp = svc.handle(req).await.unwrap();
637 assert_eq!(resp.status, StatusCode::OK);
638 let body: Value = serde_json::from_slice(&resp.body).unwrap();
639 assert_eq!(body["Configuration"]["FunctionName"], "my-func");
640 }
641
642 #[tokio::test]
643 async fn test_delete_function() {
644 let state = make_state();
645 let svc = LambdaService::new(state);
646
647 let create_body = json!({
648 "FunctionName": "to-delete",
649 "Runtime": "nodejs20.x",
650 "Role": "arn:aws:iam::123456789012:role/test",
651 "Handler": "index.handler",
652 "Code": {}
653 });
654
655 let req = make_request(
656 Method::POST,
657 "/2015-03-31/functions",
658 &create_body.to_string(),
659 );
660 svc.handle(req).await.unwrap();
661
662 let req = make_request(Method::DELETE, "/2015-03-31/functions/to-delete", "");
663 let resp = svc.handle(req).await.unwrap();
664 assert_eq!(resp.status, StatusCode::NO_CONTENT);
665
666 let req = make_request(Method::GET, "/2015-03-31/functions/to-delete", "");
668 let resp = svc.handle(req).await;
669 assert!(resp.is_err());
670 }
671
672 #[tokio::test]
673 async fn test_invoke_without_runtime_returns_error() {
674 let state = make_state();
675 let svc = LambdaService::new(state);
676
677 let create_body = json!({
678 "FunctionName": "invoke-me",
679 "Runtime": "python3.12",
680 "Role": "arn:aws:iam::123456789012:role/test",
681 "Handler": "index.handler",
682 "Code": {}
683 });
684
685 let req = make_request(
686 Method::POST,
687 "/2015-03-31/functions",
688 &create_body.to_string(),
689 );
690 svc.handle(req).await.unwrap();
691
692 let req = make_request(
693 Method::POST,
694 "/2015-03-31/functions/invoke-me/invocations",
695 r#"{"key": "value"}"#,
696 );
697 let resp = svc.handle(req).await;
698 assert!(resp.is_err());
699 }
700
701 #[tokio::test]
702 async fn test_invoke_nonexistent_function() {
703 let state = make_state();
704 let svc = LambdaService::new(state);
705
706 let req = make_request(
707 Method::POST,
708 "/2015-03-31/functions/does-not-exist/invocations",
709 "{}",
710 );
711 let resp = svc.handle(req).await;
712 assert!(resp.is_err());
713 }
714
715 #[tokio::test]
716 async fn test_list_functions() {
717 let state = make_state();
718 let svc = LambdaService::new(state);
719
720 for name in &["func-a", "func-b"] {
721 let create_body = json!({
722 "FunctionName": name,
723 "Runtime": "python3.12",
724 "Role": "arn:aws:iam::123456789012:role/test",
725 "Handler": "index.handler",
726 "Code": {}
727 });
728 let req = make_request(
729 Method::POST,
730 "/2015-03-31/functions",
731 &create_body.to_string(),
732 );
733 svc.handle(req).await.unwrap();
734 }
735
736 let req = make_request(Method::GET, "/2015-03-31/functions", "");
737 let resp = svc.handle(req).await.unwrap();
738 let body: Value = serde_json::from_slice(&resp.body).unwrap();
739 assert_eq!(body["Functions"].as_array().unwrap().len(), 2);
740 }
741
742 #[tokio::test]
743 async fn test_event_source_mapping() {
744 let state = make_state();
745 let svc = LambdaService::new(state);
746
747 let create_body = json!({
749 "FunctionName": "esm-func",
750 "Runtime": "python3.12",
751 "Role": "arn:aws:iam::123456789012:role/test",
752 "Handler": "index.handler",
753 "Code": {}
754 });
755 let req = make_request(
756 Method::POST,
757 "/2015-03-31/functions",
758 &create_body.to_string(),
759 );
760 svc.handle(req).await.unwrap();
761
762 let mapping_body = json!({
764 "FunctionName": "esm-func",
765 "EventSourceArn": "arn:aws:sqs:us-east-1:123456789012:my-queue",
766 "BatchSize": 5
767 });
768 let req = make_request(
769 Method::POST,
770 "/2015-03-31/event-source-mappings",
771 &mapping_body.to_string(),
772 );
773 let resp = svc.handle(req).await.unwrap();
774 assert_eq!(resp.status, StatusCode::ACCEPTED);
775 let body: Value = serde_json::from_slice(&resp.body).unwrap();
776 let uuid = body["UUID"].as_str().unwrap().to_string();
777
778 let req = make_request(Method::GET, "/2015-03-31/event-source-mappings", "");
780 let resp = svc.handle(req).await.unwrap();
781 let body: Value = serde_json::from_slice(&resp.body).unwrap();
782 assert_eq!(body["EventSourceMappings"].as_array().unwrap().len(), 1);
783
784 let req = make_request(
786 Method::DELETE,
787 &format!("/2015-03-31/event-source-mappings/{uuid}"),
788 "",
789 );
790 let resp = svc.handle(req).await.unwrap();
791 assert_eq!(resp.status, StatusCode::ACCEPTED);
792 }
793}