From 04275beedd493b9607f3ec8464f7cca51a7cb7ce Mon Sep 17 00:00:00 2001 From: Giorgos Nikolaou Date: Tue, 17 Mar 2026 19:35:05 +0100 Subject: [PATCH 1/4] Add lambda tests --- .github/workflows/lambda-unit-tests.yml | 36 ++ .gitignore | 3 +- .../backend_lamdas/tests/test_lambdas.py | 469 ++++++++++++++++++ 3 files changed, 507 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/lambda-unit-tests.yml create mode 100644 lifewatch_batch_platform/terraform/backend_lamdas/tests/test_lambdas.py diff --git a/.github/workflows/lambda-unit-tests.yml b/.github/workflows/lambda-unit-tests.yml new file mode 100644 index 0000000..b9c914f --- /dev/null +++ b/.github/workflows/lambda-unit-tests.yml @@ -0,0 +1,36 @@ +name: Lambda Unit Tests +# Runs Python unit tests for backend lambdas. + +on: + push: + paths: + - "lifewatch_batch_platform/terraform/backend_lamdas/**" + - ".github/workflows/lambda-unit-tests.yml" + pull_request: + paths: + - "lifewatch_batch_platform/terraform/backend_lamdas/**" + - ".github/workflows/lambda-unit-tests.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + test-lambda-handlers: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Run Lambda Unit Tests + run: | + python -m unittest discover \ + -s lifewatch_batch_platform/terraform/backend_lamdas/tests \ + -p "test_*.py" \ + -v diff --git a/.gitignore b/.gitignore index 00c0622..772f1d6 100644 --- a/.gitignore +++ b/.gitignore @@ -60,4 +60,5 @@ terraform.rc /server/staticfiles /server/jobs/tests/__pycache__ *.pyc -server/zappa_settings.json \ No newline at end of file +server/zappa_settings.json +/.env diff --git a/lifewatch_batch_platform/terraform/backend_lamdas/tests/test_lambdas.py b/lifewatch_batch_platform/terraform/backend_lamdas/tests/test_lambdas.py new file mode 100644 index 0000000..9e3e91f --- /dev/null +++ b/lifewatch_batch_platform/terraform/backend_lamdas/tests/test_lambdas.py @@ -0,0 +1,469 @@ +import json +import os +import sys +import unittest +from datetime import datetime, timezone +from email.generator import BytesGenerator +from email.mime.application import MIMEApplication +from email.mime.multipart import MIMEMultipart +from io import BytesIO +from pathlib import Path +from types import ModuleType +from types import SimpleNamespace +from unittest.mock import Mock, patch + + +class ClientError(Exception): + def __init__(self, error_response, operation_name): + super().__init__(str(error_response)) + self.response = error_response + self.operation_name = operation_name + + +fake_boto3 = ModuleType("boto3") +fake_boto3.client = lambda _service_name: Mock() +sys.modules["boto3"] = fake_boto3 + +fake_botocore_exceptions = ModuleType("botocore.exceptions") +fake_botocore_exceptions.ClientError = ClientError +fake_botocore = ModuleType("botocore") +fake_botocore.exceptions = fake_botocore_exceptions +sys.modules["botocore"] = fake_botocore +sys.modules["botocore.exceptions"] = fake_botocore_exceptions + +BASE_DIR = Path(__file__).resolve().parents[1] +if str(BASE_DIR) not in sys.path: + sys.path.insert(0, str(BASE_DIR)) + +os.environ.setdefault("BUCKET", "test-bucket") + +import history_list +import lambda_function +import logs +import results +import status + + +def parse_body(lambda_response): + return json.loads(lambda_response["body"]) + + +def build_multipart_body(parts): + msg = MIMEMultipart("form-data") + for part in parts: + payload = part.get("content", b"") + if isinstance(payload, str): + payload = payload.encode("utf-8") + mime_part = MIMEApplication(payload) + + disposition = f'form-data; name="{part["name"]}"' + if part.get("filename"): + disposition += f'; filename="{part["filename"]}"' + mime_part.add_header("Content-Disposition", disposition) + if part.get("content_type"): + mime_part.add_header("Content-Type", part["content_type"]) + + msg.attach(mime_part) + + buff = BytesIO() + generator = BytesGenerator(buff, mangle_from_=False, maxheaderlen=0) + generator.flatten(msg) + return msg.get("Content-Type"), buff.getvalue().decode("utf-8") + + +class TestSubmitLambda(unittest.TestCase): + def test_lambda_handler_rejects_non_multipart(self): + response = lambda_function.lambda_handler({"headers": {}, "body": "{}"}, None) + self.assertEqual(response["statusCode"], 400) + self.assertEqual(parse_body(response)["error"], "Request must be multipart/form-data") + + def test_lambda_handler_submits_job_for_minimal_valid_payload(self): + lambda_function.JOB_PROFILES_CONFIG = { + "standard": { + "queue": "queue-arn", + "definition": "definition-arn", + } + } + + notebook = { + "metadata": {"language_info": {"name": "python"}}, + "cells": [ + { + "cell_type": "code", + "source": [ + "param_limit = 1\n", + "print('ok')\n", + ], + } + ], + } + + content_type, body = build_multipart_body( + [ + { + "name": "notebook", + "filename": "demo.ipynb", + "content_type": "application/json", + "content": json.dumps(notebook), + }, + { + "name": "param_limit", + "content": "5", + }, + { + "name": "execution_profile", + "content": "standard", + }, + ] + ) + + s3_mock = Mock() + batch_mock = Mock() + batch_mock.submit_job.return_value = {"jobId": "batch-123"} + + with patch.object(lambda_function, "s3", s3_mock), patch.object(lambda_function, "batch", batch_mock), patch.object(lambda_function.uuid, "uuid4", return_value="job-123"): + response = lambda_function.lambda_handler( + { + "headers": {"content-type": content_type}, + "body": body, + "isBase64Encoded": False, + }, + None, + ) + + payload = parse_body(response) + self.assertEqual(response["statusCode"], 200) + self.assertEqual(payload["job_id"], "job-123") + self.assertEqual(payload["execution_profile"], "standard") + self.assertEqual(s3_mock.put_object.call_count, 2) + batch_mock.submit_job.assert_called_once() + + def test_lambda_handler_rejects_missing_notebook_part(self): + lambda_function.JOB_PROFILES_CONFIG = { + "standard": { + "queue": "queue-arn", + "definition": "definition-arn", + } + } + + content_type, body = build_multipart_body( + [ + { + "name": "param_limit", + "content": "5", + }, + { + "name": "execution_profile", + "content": "standard", + }, + ] + ) + + response = lambda_function.lambda_handler( + { + "headers": {"content-type": content_type}, + "body": body, + "isBase64Encoded": False, + }, + None, + ) + + self.assertEqual(response["statusCode"], 400) + self.assertEqual(parse_body(response)["error"], "Missing mandatory 'notebook' file.") + + def test_lambda_handler_uploads_environment_and_input_files(self): + lambda_function.JOB_PROFILES_CONFIG = { + "standard": { + "queue": "queue-arn", + "definition": "definition-arn", + } + } + + notebook = { + "metadata": {"language_info": {"name": "python"}}, + "cells": [{"cell_type": "code", "source": ["param_limit = 1\n"]}], + } + + content_type, body = build_multipart_body( + [ + { + "name": "notebook", + "filename": "demo.ipynb", + "content_type": "application/json", + "content": json.dumps(notebook), + }, + { + "name": "environment", + "filename": "environment.yaml", + "content_type": "text/yaml", + "content": "name: test-env\n", + }, + { + "name": "dataset", + "filename": "input.csv", + "content_type": "text/csv", + "content": "a,b\n1,2\n", + }, + { + "name": "execution_profile", + "content": "standard", + }, + ] + ) + + s3_mock = Mock() + batch_mock = Mock() + batch_mock.submit_job.return_value = {"jobId": "batch-123"} + + with patch.object(lambda_function, "s3", s3_mock), patch.object(lambda_function, "batch", batch_mock), patch.object(lambda_function.uuid, "uuid4", return_value="job-456"): + response = lambda_function.lambda_handler( + { + "headers": {"content-type": content_type}, + "body": body, + "isBase64Encoded": False, + }, + None, + ) + + self.assertEqual(response["statusCode"], 200) + self.assertEqual(s3_mock.put_object.call_count, 4) + + uploaded_keys = [call.kwargs["Key"] for call in s3_mock.put_object.call_args_list] + self.assertIn("jobs/job-456/environment.yaml", uploaded_keys) + self.assertIn("jobs/job-456/inputs/input.csv", uploaded_keys) + + +class TestStatusLambda(unittest.TestCase): + def test_missing_job_id(self): + response = status.lambda_handler({"pathParameters": {}}, None) + self.assertEqual(response["statusCode"], 400) + + def test_successful_status_lookup(self): + body_mock = Mock() + body_mock.read.return_value = b'{"batch_job_id": "batch-abc"}' + + s3_mock = Mock() + s3_mock.get_object.return_value = {"Body": body_mock} + + batch_mock = Mock() + batch_mock.describe_jobs.return_value = { + "jobs": [ + { + "jobName": "job-1", + "status": "SUCCEEDED", + "createdAt": 1, + "startedAt": 2, + "stoppedAt": 3, + } + ] + } + + with patch.object(status, "s3", s3_mock), patch.object(status, "batch", batch_mock): + response = status.lambda_handler({"pathParameters": {"job_id": "job-1"}}, None) + + payload = parse_body(response) + self.assertEqual(response["statusCode"], 200) + self.assertEqual(payload["status"], "SUCCEEDED") + + def test_returns_404_when_batch_job_missing(self): + body_mock = Mock() + body_mock.read.return_value = b'{"batch_job_id": "batch-abc"}' + + s3_mock = Mock() + s3_mock.get_object.return_value = {"Body": body_mock} + + batch_mock = Mock() + batch_mock.describe_jobs.return_value = {"jobs": []} + + with patch.object(status, "s3", s3_mock), patch.object(status, "batch", batch_mock): + response = status.lambda_handler({"pathParameters": {"job_id": "job-1"}}, None) + + self.assertEqual(response["statusCode"], 404) + self.assertEqual(parse_body(response)["error"], "Batch Job not found in AWS") + + +class TestLogsLambda(unittest.TestCase): + def test_missing_job_id_returns_400(self): + response = logs.lambda_handler({"pathParameters": {}}, None) + self.assertEqual(response["statusCode"], 400) + + def test_returns_404_when_batch_job_missing(self): + body_mock = Mock() + body_mock.read.return_value = b'{"batch_job_id": "batch-abc"}' + + s3_mock = Mock() + s3_mock.get_object.return_value = {"Body": body_mock} + + batch_mock = Mock() + batch_mock.describe_jobs.return_value = {"jobs": []} + + with patch.object(logs, "s3", s3_mock), patch.object(logs, "batch_client", batch_mock): + response = logs.lambda_handler({"pathParameters": {"job_id": "job-1"}}, None) + + self.assertEqual(response["statusCode"], 404) + self.assertEqual(parse_body(response)["error"], "Batch Job not found in AWS") + + def test_returns_404_when_log_stream_missing(self): + body_mock = Mock() + body_mock.read.return_value = b'{"batch_job_id": "batch-abc"}' + + s3_mock = Mock() + s3_mock.get_object.return_value = {"Body": body_mock} + + batch_mock = Mock() + batch_mock.describe_jobs.return_value = {"jobs": [{"container": {}}]} + + with patch.object(logs, "s3", s3_mock), patch.object(logs, "batch_client", batch_mock): + response = logs.lambda_handler({"pathParameters": {"job_id": "job-1"}}, None) + + self.assertEqual(response["statusCode"], 404) + + def test_returns_log_messages(self): + body_mock = Mock() + body_mock.read.return_value = b'{"batch_job_id": "batch-abc"}' + + s3_mock = Mock() + s3_mock.get_object.return_value = {"Body": body_mock} + + batch_mock = Mock() + batch_mock.describe_jobs.return_value = { + "jobs": [{"container": {"logStreamName": "stream-1"}}] + } + + logs_client_mock = Mock() + logs_client_mock.get_log_events.return_value = { + "events": [{"message": "line-1"}, {"message": "line-2"}] + } + + with patch.object(logs, "s3", s3_mock), patch.object(logs, "batch_client", batch_mock), patch.object(logs, "logs_client", logs_client_mock): + response = logs.lambda_handler({"pathParameters": {"job_id": "job-1"}}, None) + + payload = parse_body(response) + self.assertEqual(response["statusCode"], 200) + self.assertEqual(payload["logs"], ["line-1", "line-2"]) + + +class TestResultsLambda(unittest.TestCase): + def test_missing_job_id_returns_400(self): + response = results.lambda_handler({"pathParameters": {}}, None) + self.assertEqual(response["statusCode"], 400) + + def test_returns_success_presigned_url_when_outputs_zip_exists(self): + s3_mock = Mock() + s3_mock.generate_presigned_url.return_value = "https://example/download" + + with patch.object(results, "s3", s3_mock): + response = results.lambda_handler({"pathParameters": {"job_id": "job-1"}}, None) + + payload = parse_body(response) + self.assertEqual(response["statusCode"], 200) + self.assertEqual(payload["status"], "success") + + def test_falls_back_to_failed_outputs(self): + not_found_error = ClientError( + {"Error": {"Code": "404", "Message": "Not found"}}, + "HeadObject", + ) + + s3_mock = Mock() + s3_mock.head_object.side_effect = [not_found_error, None] + s3_mock.generate_presigned_url.return_value = "https://example/download-failed" + + with patch.object(results, "s3", s3_mock): + response = results.lambda_handler({"pathParameters": {"job_id": "job-1"}}, None) + + payload = parse_body(response) + self.assertEqual(response["statusCode"], 200) + self.assertEqual(payload["status"], "partial_failure") + + def test_returns_404_when_no_outputs_exist(self): + not_found_error = ClientError( + {"Error": {"Code": "404", "Message": "Not found"}}, + "HeadObject", + ) + + s3_mock = Mock() + s3_mock.head_object.side_effect = [not_found_error, not_found_error] + + with patch.object(results, "s3", s3_mock): + response = results.lambda_handler({"pathParameters": {"job_id": "job-1"}}, None) + + self.assertEqual(response["statusCode"], 404) + self.assertIn("No outputs found for job job-1", parse_body(response)["error"]) + + +class TestHistoryListLambda(unittest.TestCase): + def test_builds_history_items_and_maps_batch_status(self): + body_mock = Mock() + body_mock.read.return_value = b'{"batch_job_id": "batch-abc", "execution_profile": "standard"}' + + s3_mock = Mock() + paginator_mock = Mock() + paginator_mock.paginate.return_value = [{"CommonPrefixes": [{"Prefix": "jobs/job-1/"}]}] + s3_mock.get_paginator.return_value = paginator_mock + s3_mock.get_object.return_value = { + "Body": body_mock, + "LastModified": datetime(2026, 1, 1, tzinfo=timezone.utc), + } + + no_such_key = type("NoSuchKey", (Exception,), {}) + s3_mock.exceptions = SimpleNamespace(NoSuchKey=no_such_key) + + batch_mock = Mock() + batch_mock.describe_jobs.return_value = { + "jobs": [ + { + "jobId": "batch-abc", + "status": "FAILED", + "statusReason": "Container crashed", + } + ] + } + + with patch.object(history_list, "s3", s3_mock), patch.object(history_list, "batch", batch_mock): + response = history_list.lambda_handler({}, None) + + payload = parse_body(response) + self.assertEqual(response["statusCode"], 200) + self.assertEqual(len(payload["jobs"]), 1) + self.assertEqual(payload["jobs"][0]["status"], "FAILED") + self.assertEqual(payload["jobs"][0]["error"], "Container crashed") + + def test_maps_non_failed_status_reason_to_info(self): + body_mock = Mock() + body_mock.read.return_value = b'{"batch_job_id": "batch-abc", "execution_profile": "standard"}' + + s3_mock = Mock() + paginator_mock = Mock() + paginator_mock.paginate.return_value = [{"CommonPrefixes": [{"Prefix": "jobs/job-2/"}]}] + s3_mock.get_paginator.return_value = paginator_mock + s3_mock.get_object.return_value = { + "Body": body_mock, + "LastModified": datetime(2026, 1, 2, tzinfo=timezone.utc), + } + + no_such_key = type("NoSuchKey", (Exception,), {}) + s3_mock.exceptions = SimpleNamespace(NoSuchKey=no_such_key) + + batch_mock = Mock() + batch_mock.describe_jobs.return_value = { + "jobs": [ + { + "jobId": "batch-abc", + "status": "RUNNING", + "statusReason": "Starting container", + } + ] + } + + with patch.object(history_list, "s3", s3_mock), patch.object(history_list, "batch", batch_mock): + response = history_list.lambda_handler({}, None) + + payload = parse_body(response) + self.assertEqual(response["statusCode"], 200) + self.assertEqual(payload["jobs"][0]["status"], "RUNNING") + self.assertEqual(payload["jobs"][0]["info"], "Starting container") + + +if __name__ == "__main__": + unittest.main() From 3e0311ba6e808aa6fcff77111d4d53328f167d48 Mon Sep 17 00:00:00 2001 From: Giorgos Nikolaou Date: Tue, 17 Mar 2026 19:42:05 +0100 Subject: [PATCH 2/4] Fix typo and add pipeline --- .github/workflows/lambda-unit-tests.yml | 6 +++--- compress_lambdas.ps1 | 2 +- compress_lambdas.sh | 2 +- .../{backend_lamdas => backend_lambdas}/handle_cors.py | 0 .../{backend_lamdas => backend_lambdas}/history_list.py | 0 .../{backend_lamdas => backend_lambdas}/lambda_function.py | 0 .../terraform/{backend_lamdas => backend_lambdas}/logs.py | 0 .../{backend_lamdas => backend_lambdas}/results.py | 0 .../terraform/{backend_lamdas => backend_lambdas}/status.py | 0 .../tests/test_lambdas.py | 0 10 files changed, 5 insertions(+), 5 deletions(-) rename lifewatch_batch_platform/terraform/{backend_lamdas => backend_lambdas}/handle_cors.py (100%) rename lifewatch_batch_platform/terraform/{backend_lamdas => backend_lambdas}/history_list.py (100%) rename lifewatch_batch_platform/terraform/{backend_lamdas => backend_lambdas}/lambda_function.py (100%) rename lifewatch_batch_platform/terraform/{backend_lamdas => backend_lambdas}/logs.py (100%) rename lifewatch_batch_platform/terraform/{backend_lamdas => backend_lambdas}/results.py (100%) rename lifewatch_batch_platform/terraform/{backend_lamdas => backend_lambdas}/status.py (100%) rename lifewatch_batch_platform/terraform/{backend_lamdas => backend_lambdas}/tests/test_lambdas.py (100%) diff --git a/.github/workflows/lambda-unit-tests.yml b/.github/workflows/lambda-unit-tests.yml index b9c914f..78acf74 100644 --- a/.github/workflows/lambda-unit-tests.yml +++ b/.github/workflows/lambda-unit-tests.yml @@ -4,11 +4,11 @@ name: Lambda Unit Tests on: push: paths: - - "lifewatch_batch_platform/terraform/backend_lamdas/**" + - "lifewatch_batch_platform/terraform/backend_lambdas/**" - ".github/workflows/lambda-unit-tests.yml" pull_request: paths: - - "lifewatch_batch_platform/terraform/backend_lamdas/**" + - "lifewatch_batch_platform/terraform/backend_lambdas/**" - ".github/workflows/lambda-unit-tests.yml" workflow_dispatch: @@ -31,6 +31,6 @@ jobs: - name: Run Lambda Unit Tests run: | python -m unittest discover \ - -s lifewatch_batch_platform/terraform/backend_lamdas/tests \ + -s lifewatch_batch_platform/terraform/backend_lambdas/tests \ -p "test_*.py" \ -v diff --git a/compress_lambdas.ps1 b/compress_lambdas.ps1 index 0b64733..adf4139 100644 --- a/compress_lambdas.ps1 +++ b/compress_lambdas.ps1 @@ -1,4 +1,4 @@ -$SourceDir = ".\lifewatch_batch_platform\terraform\backend_lamdas" +$SourceDir = ".\lifewatch_batch_platform\terraform\backend_lambdas" $TargetDir = ".\lifewatch_batch_platform\terraform\backend_lambda_artifacts" New-Item -ItemType Directory -Force -Path $TargetDir | Out-Null diff --git a/compress_lambdas.sh b/compress_lambdas.sh index 8dc024b..589bd87 100644 --- a/compress_lambdas.sh +++ b/compress_lambdas.sh @@ -1,6 +1,6 @@ #!/bin/bash -SOURCE_DIR="./lifewatch_batch_platform/terraform/backend_lamdas" +SOURCE_DIR="./lifewatch_batch_platform/terraform/backend_lambdas" TARGET_DIR="./lifewatch_batch_platform/terraform/backend_lambda_artifacts" mkdir -p "$TARGET_DIR" diff --git a/lifewatch_batch_platform/terraform/backend_lamdas/handle_cors.py b/lifewatch_batch_platform/terraform/backend_lambdas/handle_cors.py similarity index 100% rename from lifewatch_batch_platform/terraform/backend_lamdas/handle_cors.py rename to lifewatch_batch_platform/terraform/backend_lambdas/handle_cors.py diff --git a/lifewatch_batch_platform/terraform/backend_lamdas/history_list.py b/lifewatch_batch_platform/terraform/backend_lambdas/history_list.py similarity index 100% rename from lifewatch_batch_platform/terraform/backend_lamdas/history_list.py rename to lifewatch_batch_platform/terraform/backend_lambdas/history_list.py diff --git a/lifewatch_batch_platform/terraform/backend_lamdas/lambda_function.py b/lifewatch_batch_platform/terraform/backend_lambdas/lambda_function.py similarity index 100% rename from lifewatch_batch_platform/terraform/backend_lamdas/lambda_function.py rename to lifewatch_batch_platform/terraform/backend_lambdas/lambda_function.py diff --git a/lifewatch_batch_platform/terraform/backend_lamdas/logs.py b/lifewatch_batch_platform/terraform/backend_lambdas/logs.py similarity index 100% rename from lifewatch_batch_platform/terraform/backend_lamdas/logs.py rename to lifewatch_batch_platform/terraform/backend_lambdas/logs.py diff --git a/lifewatch_batch_platform/terraform/backend_lamdas/results.py b/lifewatch_batch_platform/terraform/backend_lambdas/results.py similarity index 100% rename from lifewatch_batch_platform/terraform/backend_lamdas/results.py rename to lifewatch_batch_platform/terraform/backend_lambdas/results.py diff --git a/lifewatch_batch_platform/terraform/backend_lamdas/status.py b/lifewatch_batch_platform/terraform/backend_lambdas/status.py similarity index 100% rename from lifewatch_batch_platform/terraform/backend_lamdas/status.py rename to lifewatch_batch_platform/terraform/backend_lambdas/status.py diff --git a/lifewatch_batch_platform/terraform/backend_lamdas/tests/test_lambdas.py b/lifewatch_batch_platform/terraform/backend_lambdas/tests/test_lambdas.py similarity index 100% rename from lifewatch_batch_platform/terraform/backend_lamdas/tests/test_lambdas.py rename to lifewatch_batch_platform/terraform/backend_lambdas/tests/test_lambdas.py From 4d8827c0270e35c83802d58e436bb22562100219 Mon Sep 17 00:00:00 2001 From: Giorgos Nikolaou Date: Tue, 17 Mar 2026 19:51:35 +0100 Subject: [PATCH 3/4] Update actions to prevent multi-runs --- .github/workflows/deploy-worker-ecr.yml | 7 +++++-- .github/workflows/e2e-notebook-deploy-and-run.yml | 4 +--- .github/workflows/lambda-unit-tests.yml | 6 ++++++ .../workflows/smoke-test-worker-containerization.yml | 10 ++++++++++ .github/workflows/test-terraform-plan.yml | 6 ++++-- 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-worker-ecr.yml b/.github/workflows/deploy-worker-ecr.yml index e65b07b..d1e266e 100644 --- a/.github/workflows/deploy-worker-ecr.yml +++ b/.github/workflows/deploy-worker-ecr.yml @@ -17,13 +17,16 @@ env: ECR_REPOSITORY: r-notebook-worker concurrency: - group: deploy-worker-ecr-main + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref_name }} cancel-in-progress: true jobs: deploy-worker: name: Build and Push Worker Image - if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' + if: >- + github.event_name == 'workflow_dispatch' || + (github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.head_branch == 'main') runs-on: ubuntu-latest steps: diff --git a/.github/workflows/e2e-notebook-deploy-and-run.yml b/.github/workflows/e2e-notebook-deploy-and-run.yml index 0e168c4..b42c308 100644 --- a/.github/workflows/e2e-notebook-deploy-and-run.yml +++ b/.github/workflows/e2e-notebook-deploy-and-run.yml @@ -15,7 +15,7 @@ env: TERRAFORM_ENV_DIR: ${{ secrets.TERRAFORM_ENV_DIR }} concurrency: - group: terraform-batch-dev-state + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref_name }} cancel-in-progress: false jobs: @@ -23,9 +23,7 @@ jobs: name: Terraform Deploy + Notebook E2E if: >- github.event_name == 'workflow_dispatch' || - github.event_name == 'push' || (github.event.workflow_run.conclusion == 'success' && - github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main') runs-on: ubuntu-latest diff --git a/.github/workflows/lambda-unit-tests.yml b/.github/workflows/lambda-unit-tests.yml index 78acf74..0d374c4 100644 --- a/.github/workflows/lambda-unit-tests.yml +++ b/.github/workflows/lambda-unit-tests.yml @@ -3,6 +3,8 @@ name: Lambda Unit Tests on: push: + branches: + - main paths: - "lifewatch_batch_platform/terraform/backend_lambdas/**" - ".github/workflows/lambda-unit-tests.yml" @@ -15,6 +17,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test-lambda-handlers: runs-on: ubuntu-latest diff --git a/.github/workflows/smoke-test-worker-containerization.yml b/.github/workflows/smoke-test-worker-containerization.yml index cb62bb2..45e9dad 100644 --- a/.github/workflows/smoke-test-worker-containerization.yml +++ b/.github/workflows/smoke-test-worker-containerization.yml @@ -2,6 +2,12 @@ name: Smoke Test Worker Containerization # This workflow builds the worker and runs a smoke test to verify that the containerization is working correctly. on: + push: + branches: + - main + paths: + - "worker/**" + - ".github/workflows/smoke-test-worker-containerization.yml" pull_request: paths: - "worker/**" @@ -11,6 +17,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: verify-worker-pod: runs-on: ubuntu-latest diff --git a/.github/workflows/test-terraform-plan.yml b/.github/workflows/test-terraform-plan.yml index 2f8c0b8..3937364 100644 --- a/.github/workflows/test-terraform-plan.yml +++ b/.github/workflows/test-terraform-plan.yml @@ -2,6 +2,8 @@ name: "Terraform CI" on: push: + branches: + - main paths: - 'lifewatch_batch_platform/terraform/**' - '.github/workflows/test-terraform-plan.yml' @@ -10,8 +12,8 @@ on: - 'lifewatch_batch_platform/terraform/**' - '.github/workflows/test-terraform-plan.yml' concurrency: - group: terraform-batch-dev-state - cancel-in-progress: false + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true permissions: contents: read From 5f2d8cf3e2941c34aa23b83c383d60e31952454c Mon Sep 17 00:00:00 2001 From: Giorgos Nikolaou Date: Tue, 17 Mar 2026 19:54:24 +0100 Subject: [PATCH 4/4] Update smoke-test-worker-containerization.yml --- .github/workflows/smoke-test-worker-containerization.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/smoke-test-worker-containerization.yml b/.github/workflows/smoke-test-worker-containerization.yml index 45e9dad..fc78f16 100644 --- a/.github/workflows/smoke-test-worker-containerization.yml +++ b/.github/workflows/smoke-test-worker-containerization.yml @@ -5,9 +5,6 @@ on: push: branches: - main - paths: - - "worker/**" - - ".github/workflows/smoke-test-worker-containerization.yml" pull_request: paths: - "worker/**"