diff --git a/.github/workflows/code-checks.yaml b/.github/workflows/code-checks.yaml index e8249a1..1c0a269 100644 --- a/.github/workflows/code-checks.yaml +++ b/.github/workflows/code-checks.yaml @@ -14,14 +14,13 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@v4 - - name: Install poetry - run: pipx install poetry - uses: actions/setup-python@v5 with: python-version: '3.11.10' - cache: 'poetry' + - name: Install poetry + run: pip install poetry - name: Install dependencies - run: poetry install + run: python -m poetry install - name: Ruff check run: poetry run ruff check . - name: Ruff format check @@ -33,14 +32,13 @@ jobs: timeout-minutes: 60 steps: - uses: actions/checkout@v4 - - name: Install poetry - run: pipx install poetry - uses: actions/setup-python@v5 with: python-version: '3.11.10' - cache: 'poetry' + - name: Install poetry + run: pip install poetry - name: Install dependencies - run: poetry install + run: python -m poetry install - name: configure AWS uses: aws-actions/configure-aws-credentials@v4 with: diff --git a/api/endpoints/job_update.py b/api/endpoints/job_update.py index f033726..ef5034b 100644 --- a/api/endpoints/job_update.py +++ b/api/endpoints/job_update.py @@ -1,3 +1,5 @@ +import datetime + from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session @@ -24,7 +26,21 @@ def update_job_status( db_job = job_crud.get_job(db, update.job_id) if db_job is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + + # Update the status db_job.status = update.status.value + + # Update date_started when job is first pulled/started + if update.status == JobStates.pulled and db_job.date_started is None: + db_job.date_started = datetime.datetime.now(datetime.timezone.utc) + + # Update date_finished when job reaches a terminal state + if ( + update.status in [JobStates.finished, JobStates.error] + and db_job.date_finished is None + ): + db_job.date_finished = datetime.datetime.now(datetime.timezone.utc) + if update.runtime_details: db_job.runtime_details = ( (db_job.runtime_details or "") + "\n" + update.runtime_details diff --git a/pyproject.toml b/pyproject.toml index df59d6e..f11ad0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "api" -version = "0.1.0" +version = "0.1.1" description = "User-facing API of DECODE OpenCloud." authors = ["Arthur Jaques "] readme = "README.md" diff --git a/tests/integration/endpoints/test_job_update.py b/tests/integration/endpoints/test_job_update.py index 280490d..491b668 100644 --- a/tests/integration/endpoints/test_job_update.py +++ b/tests/integration/endpoints/test_job_update.py @@ -62,3 +62,138 @@ def test_finished_notification( headers={"x-api-key": internal_api_key_secret}, ) mock_email_sender.send_email.assert_called_once() + + +def test_date_started_set_on_pulled_status( + client: TestClient, + db_session: Session, + internal_api_key_secret: str, + jobs: list[Job], +) -> None: + """Test that date_started is set when job status changes to 'pulled'.""" + job = jobs[0] + assert job.date_started is None # Initially no start date + + response = client.put( + ENDPOINT, + json={"job_id": job.id, "status": "pulled"}, + headers={"x-api-key": internal_api_key_secret}, + ) + assert response.status_code == 200 + + # Refresh job from database + db_session.refresh(job) + assert job.status == "pulled" + assert job.date_started is not None # Should now have a start date + assert job.date_finished is None # Should not have finished date yet + + +def test_date_started_not_overwritten( + client: TestClient, + db_session: Session, + internal_api_key_secret: str, + jobs: list[Job], +) -> None: + """Test that date_started is not overwritten if already set.""" + job = jobs[0] + + # First update to 'pulled' to set date_started + client.put( + ENDPOINT, + json={"job_id": job.id, "status": "pulled"}, + headers={"x-api-key": internal_api_key_secret}, + ) + db_session.refresh(job) + original_start_date = job.date_started + assert original_start_date is not None + + # Update to another status and then back to pulled + client.put( + ENDPOINT, + json={"job_id": job.id, "status": "running"}, + headers={"x-api-key": internal_api_key_secret}, + ) + client.put( + ENDPOINT, + json={"job_id": job.id, "status": "pulled"}, + headers={"x-api-key": internal_api_key_secret}, + ) + + db_session.refresh(job) + assert job.date_started == original_start_date # Should not change + + +def test_date_finished_set_on_finished_status( + client: TestClient, + db_session: Session, + internal_api_key_secret: str, + jobs: list[Job], +) -> None: + """Test that date_finished is set when job status changes to 'finished'.""" + job = jobs[0] + assert job.date_finished is None # Initially no finish date + + response = client.put( + ENDPOINT, + json={"job_id": job.id, "status": "finished"}, + headers={"x-api-key": internal_api_key_secret}, + ) + assert response.status_code == 200 + + # Refresh job from database + db_session.refresh(job) + assert job.status == "finished" + assert job.date_finished is not None # Should now have a finish date + + +def test_date_finished_set_on_error_status( + client: TestClient, + db_session: Session, + internal_api_key_secret: str, + jobs: list[Job], +) -> None: + """Test that date_finished is set when job status changes to 'error'.""" + job = jobs[0] + assert job.date_finished is None # Initially no finish date + + response = client.put( + ENDPOINT, + json={"job_id": job.id, "status": "error"}, + headers={"x-api-key": internal_api_key_secret}, + ) + assert response.status_code == 200 + + # Refresh job from database + db_session.refresh(job) + assert job.status == "error" + assert job.date_finished is not None # Should now have a finish date + + +def test_date_finished_not_overwritten( + client: TestClient, + db_session: Session, + internal_api_key_secret: str, + jobs: list[Job], +) -> None: + """Test that date_finished is not overwritten if already set.""" + job = jobs[0] + + # First update to 'finished' to set date_finished + client.put( + ENDPOINT, + json={"job_id": job.id, "status": "finished"}, + headers={"x-api-key": internal_api_key_secret}, + ) + db_session.refresh(job) + original_finish_date = job.date_finished + assert original_finish_date is not None + + # Update to error status (should not overwrite date_finished) + client.put( + ENDPOINT, + json={"job_id": job.id, "status": "error"}, + headers={"x-api-key": internal_api_key_secret}, + ) + + db_session.refresh(job) + assert job.date_finished == original_finish_date # Should not change