@@ -248,28 +248,46 @@ def test_binary_selection_by_architecture(self, arch, expected_binary, mock_get_
248248 ),
249249 ]
250250 )
251- @patch ("samcli.local.docker.durable_functions_emulator_container.is_image_current " )
251+ @patch ("samcli.local.docker.durable_functions_emulator_container._get_host_architecture " )
252252 @patch ("os.makedirs" )
253253 @patch ("os.getcwd" )
254+ @patch ("pathlib.Path.exists" )
254255 def test_create_container (
255- self , name , env_vars , expected_port , expected_store , expected_scale , mock_getcwd , mock_makedirs , mock_is_current
256+ self ,
257+ name ,
258+ env_vars ,
259+ expected_port ,
260+ expected_store ,
261+ expected_scale ,
262+ mock_path_exists ,
263+ mock_getcwd ,
264+ mock_makedirs ,
265+ mock_get_host_arch ,
256266 ):
257267 """Test container creation with all configuration permutations"""
258- mock_is_current .return_value = True
268+ mock_get_host_arch .return_value = "x86_64"
259269 test_dir = "/test/dir"
260270 mock_getcwd .return_value = test_dir
271+ mock_path_exists .return_value = True
272+
273+ # Mock image already exists
274+ mock_image = Mock ()
275+ self .mock_docker_client .images .get .return_value = mock_image
261276
262277 with patch .dict ("os.environ" , env_vars , clear = True ):
263278 container = self ._create_container ()
279+ container ._RAPID_SOURCE_PATH = Path (__file__ ).parent
264280 container ._wait_for_ready = Mock ()
265281 container .start ()
266282
267283 # Verify container was created
268284 self .mock_docker_client .containers .create .assert_called_once ()
269285 call_args = self .mock_docker_client .containers .create .call_args
270286
271- # Verify image and working directory
272- self .assertEqual (call_args .kwargs ["image" ], DurableFunctionsEmulatorContainer ._EMULATOR_IMAGE )
287+ # Verify built image is used
288+ self .assertEqual (
289+ call_args .kwargs ["image" ], "samcli/durable-execution-emulator:aws-durable-execution-emulator-x86_64"
290+ )
273291 self .assertEqual (call_args .kwargs ["working_dir" ], "/tmp/.durable-executions-local" )
274292
275293 # Verify port configuration
@@ -306,6 +324,132 @@ def test_start_raises_error_when_binary_not_found(self):
306324 container .start ()
307325 self .assertIn ("Durable Functions Emulator binary not found" , str (context .exception ))
308326
327+ @parameterized .expand (
328+ [
329+ (
330+ "x86_64" ,
331+ "aws-durable-execution-emulator-x86_64" ,
332+ "samcli/durable-execution-emulator:aws-durable-execution-emulator-x86_64" ,
333+ ),
334+ (
335+ "arm64" ,
336+ "aws-durable-execution-emulator-arm64" ,
337+ "samcli/durable-execution-emulator:aws-durable-execution-emulator-arm64" ,
338+ ),
339+ ]
340+ )
341+ @patch ("samcli.local.docker.durable_functions_emulator_container._get_host_architecture" )
342+ @patch ("samcli.local.docker.durable_functions_emulator_container.create_tarball" )
343+ @patch ("samcli.local.docker.durable_functions_emulator_container.get_tar_filter_for_windows" )
344+ @patch ("builtins.open" , new_callable = mock_open )
345+ @patch ("os.unlink" )
346+ @patch ("pathlib.Path.exists" )
347+ def test_build_emulator_image_creates_new_image (
348+ self ,
349+ arch ,
350+ binary_name ,
351+ expected_tag ,
352+ mock_path_exists ,
353+ mock_unlink ,
354+ mock_file ,
355+ mock_tar_filter ,
356+ mock_create_tarball ,
357+ mock_get_host_arch ,
358+ ):
359+ """Test building emulator image when it doesn't exist, including dockerfile generation and image tag"""
360+ mock_get_host_arch .return_value = arch
361+ mock_tar_filter .return_value = None
362+ mock_tarball = Mock ()
363+ mock_create_tarball .return_value .__enter__ .return_value = mock_tarball
364+ mock_path_exists .return_value = True
365+
366+ # Mock image doesn't exist
367+ self .mock_docker_client .images .get .side_effect = docker .errors .ImageNotFound ("not found" )
368+ mock_build_result = Mock ()
369+ self .mock_docker_client .images .build .return_value = mock_build_result
370+
371+ container = self ._create_container ()
372+ container ._RAPID_SOURCE_PATH = Path (__file__ ).parent
373+
374+ result = container ._build_emulator_image ()
375+
376+ # Verify image tag generation
377+ self .assertEqual (result , expected_tag )
378+ tag = container ._get_emulator_image_tag (binary_name )
379+ self .assertEqual (tag , expected_tag )
380+
381+ # Verify dockerfile generation
382+ dockerfile = container ._generate_emulator_dockerfile (binary_name )
383+ self .assertIn (f"FROM { container ._EMULATOR_IMAGE } " , dockerfile )
384+ self .assertIn (f"COPY { binary_name } /usr/local/bin/{ binary_name } " , dockerfile )
385+ self .assertIn (f"RUN chmod +x /usr/local/bin/{ binary_name } " , dockerfile )
386+
387+ # Verify image was built
388+ self .mock_docker_client .images .build .assert_called_once ()
389+ build_call = self .mock_docker_client .images .build .call_args
390+ self .assertEqual (build_call .kwargs ["tag" ], expected_tag )
391+ self .assertTrue (build_call .kwargs ["rm" ])
392+ self .assertTrue (build_call .kwargs ["custom_context" ])
393+
394+ # Verify tarball was created with correct filter
395+ mock_create_tarball .assert_called_once ()
396+
397+ @parameterized .expand (
398+ [
399+ ("x86_64" , "samcli/durable-execution-emulator:aws-durable-execution-emulator-x86_64" ),
400+ ("arm64" , "samcli/durable-execution-emulator:aws-durable-execution-emulator-arm64" ),
401+ ]
402+ )
403+ @patch ("samcli.local.docker.durable_functions_emulator_container._get_host_architecture" )
404+ @patch ("pathlib.Path.exists" )
405+ def test_build_emulator_image_reuses_existing (self , arch , expected_tag , mock_path_exists , mock_get_host_arch ):
406+ """Test that existing image is reused without rebuilding"""
407+ mock_get_host_arch .return_value = arch
408+ mock_path_exists .return_value = True
409+ mock_image = Mock ()
410+ self .mock_docker_client .images .get .return_value = mock_image
411+
412+ container = self ._create_container ()
413+ container ._RAPID_SOURCE_PATH = Path (__file__ ).parent
414+
415+ result = container ._build_emulator_image ()
416+
417+ # Verify image was not built
418+ self .mock_docker_client .images .build .assert_not_called ()
419+ self .assertEqual (result , expected_tag )
420+
421+ @parameterized .expand (
422+ [
423+ ("x86_64" , "samcli/durable-execution-emulator:aws-durable-execution-emulator-x86_64" ),
424+ ("arm64" , "samcli/durable-execution-emulator:aws-durable-execution-emulator-arm64" ),
425+ ]
426+ )
427+ @patch ("samcli.local.docker.durable_functions_emulator_container._get_host_architecture" )
428+ @patch ("os.makedirs" )
429+ @patch ("os.getcwd" )
430+ @patch ("pathlib.Path.exists" )
431+ def test_start_uses_built_image (
432+ self , arch , expected_tag , mock_path_exists , mock_getcwd , mock_makedirs , mock_get_host_arch
433+ ):
434+ """Test that start() uses the built image instead of base image"""
435+ mock_get_host_arch .return_value = arch
436+ mock_getcwd .return_value = "/test/dir"
437+ mock_path_exists .return_value = True
438+
439+ # Mock image already exists
440+ mock_image = Mock ()
441+ self .mock_docker_client .images .get .return_value = mock_image
442+
443+ container = self ._create_container ()
444+ container ._RAPID_SOURCE_PATH = Path (__file__ ).parent
445+ container ._wait_for_ready = Mock ()
446+
447+ container .start ()
448+
449+ # Verify container was created with built image tag
450+ call_args = self .mock_docker_client .containers .create .call_args
451+ self .assertEqual (call_args .kwargs ["image" ], expected_tag )
452+
309453 @parameterized .expand (
310454 [
311455 # (name, image_exists, is_current, should_pull)
0 commit comments