From 6e6f85e426f6681e9bb410a7be4672164143a0e0 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Mon, 29 Jan 2024 21:47:38 +0100 Subject: [PATCH 1/3] add actions to test and deploy --- .github/workflows/ci.yml | 108 +++++++++++++++++++++++++++++++++++++++ tests/conftest.py | 14 +++++ tests/test_app.py | 86 +++++++++++++++++++++++++++++++ tests/test_tms.py | 38 ++++++++++++++ titiler/cmr/settings.py | 1 - 5 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml create mode 100644 tests/conftest.py create mode 100644 tests/test_app.py create mode 100644 tests/test_tms.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6089c9e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,108 @@ +name: Test and Deploy + +# Triggers on pushes to main, dev and tags. +on: + workflow_dispatch: + push: + branches: + - main + - develop + tags: + - 'v*' + paths: + # Only run test and docker publish if some code have changed + - 'pyproject.toml' + - 'infrastructure/aws/**' + - 'titiler/**' + - '.pre-commit-config.yaml' + # Run tests on pull requests. + pull_request: +env: + LATEST_PY_VERSION: '3.10' + +# permissions: +# id-token: write # This is required for requesting the JWT +# contents: read # This is required for actions/checkout + +jobs: + tests: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.8', '3.9', '3.10', '3.11'] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e .["test"] + + - name: run pre-commit + if: ${{ matrix.python-version == env.LATEST_PY_VERSION }} + run: | + python -m pip install pre-commit + pre-commit run --all-files + + - name: Run tests + run: python -m pytest --cov titiler.cmr --cov-report term-missing -s -vv + + deploy: + # needs: [tests] + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags/v') + + defaults: + run: + working-directory: infrastructure/aws + + steps: + - uses: actions/checkout@v3 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::444055461661:role/github-actions-role-eodc + role-session-name: samplerolesession + aws-region: us-west-2 + + - name: Set up node + uses: actions/setup-node@v2 + with: + node-version: 18 + + - name: Install cdk + run: npm install -g aws-cdk + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -r requirements-cdk.txt + + # Build and deploy to the development environment whenever there is a push to main or dev + - name: Build & Deploy Development + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' + run: npm run cdk -- deploy titiler-cmr-staging --require-approval never + env: + # STACK_ALARM_EMAIL: ${{ secrets.ALARM_EMAIL }} + STACK_ROLE_ARN: ${{ secrets.role_arn }} + STACK_STAGE: staging + + # Build and deploy to production deployment whenever there a new tag is pushed + - name: Build & Deploy Production + if: startsWith(github.ref, 'refs/tags/v') + run: npm run cdk -- deploy titiler-cmr-production --require-approval never + env: + # STACK_ALARM_EMAIL: ${{ secrets.ALARM_EMAIL }} + STACK_ROLE_ARN: ${{ secrets.role_arn }} + STACK_STAGE: production diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..59aa8ce --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,14 @@ +"""titiler.cmr tests configuration.""" + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture +def app(monkeypatch): + """App fixture.""" + + from titiler.cmr.main import app + + with TestClient(app) as client: + yield client diff --git a/tests/test_app.py b/tests/test_app.py new file mode 100644 index 0000000..6ec4d0c --- /dev/null +++ b/tests/test_app.py @@ -0,0 +1,86 @@ +"""test titiler-cmr app.""" + + +def test_landing(app): + """Test / endpoint.""" + response = app.get("/") + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + body = response.json() + assert body["title"] == "titiler-cmr" + assert body["links"] + + response = app.get("/?f=html") + assert response.status_code == 200 + assert "text/html" in response.headers["content-type"] + assert "titiler-cmr" in response.text + + # Check accept headers + response = app.get("/", headers={"accept": "text/html"}) + assert response.status_code == 200 + assert "text/html" in response.headers["content-type"] + assert "titiler-cmr" in response.text + + # accept quality + response = app.get( + "/", headers={"accept": "application/json;q=0.9, text/html;q=1.0"} + ) + assert response.status_code == 200 + assert "text/html" in response.headers["content-type"] + assert "titiler-cmr" in response.text + + # accept quality but only json is available + response = app.get("/", headers={"accept": "text/csv;q=1.0, application/json"}) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + body = response.json() + assert body["title"] == "titiler-cmr" + + # accept quality but only json is available + response = app.get("/", headers={"accept": "text/csv;q=1.0, */*"}) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + body = response.json() + assert body["title"] == "titiler-cmr" + + # Invalid accept, return default + response = app.get("/", headers={"accept": "text/htm"}) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + body = response.json() + assert body["title"] == "titiler-cmr" + assert body["links"] + + # make sure `?f=` has priority over headers + response = app.get("/?f=json", headers={"accept": "text/html"}) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + body = response.json() + assert body["title"] == "titiler-cmr" + + +def test_docs(app): + """Test /api endpoint.""" + response = app.get("/api") + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + body = response.json() + assert body["openapi"] + + response = app.get("/api.html") + assert response.status_code == 200 + assert "text/html" in response.headers["content-type"] + + +def test_conformance(app): + """Test /conformance endpoint.""" + response = app.get("/conformance") + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + body = response.json() + assert body["conformsTo"] + + response = app.get("/conformance?f=html") + assert response.status_code == 200 + assert "text/html" in response.headers["content-type"] + assert "Conformance" in response.text diff --git a/tests/test_tms.py b/tests/test_tms.py new file mode 100644 index 0000000..033c963 --- /dev/null +++ b/tests/test_tms.py @@ -0,0 +1,38 @@ +"""test TileMatrixSets endpoints.""" + +from morecantile import tms + + +def test_tilematrix(app): + """test /tileMatrixSet endpoint.""" + response = app.get("/tileMatrixSets") + assert response.status_code == 200 + body = response.json() + + assert len(body["tileMatrixSets"]) == len(tms.list()) + tileMatrixSets = list( + filter(lambda m: m["id"] == "WebMercatorQuad", body["tileMatrixSets"]) + )[0] + assert ( + tileMatrixSets["links"][0]["href"] + == "http://testserver/tileMatrixSets/WebMercatorQuad" + ) + + response = app.get("/tileMatrixSets?f=html") + assert response.status_code == 200 + assert "text/html" in response.headers["content-type"] + + +def test_tilematrixInfo(app): + """test /tileMatrixSet endpoint.""" + response = app.get("/tileMatrixSets/WebMercatorQuad") + assert response.headers["content-type"] == "application/json" + assert response.status_code == 200 + body = response.json() + assert body["id"] == "WebMercatorQuad" + assert body["crs"] + assert body["tileMatrices"] + + response = app.get("/tileMatrixSets/WebMercatorQuad?f=html") + assert response.status_code == 200 + assert "text/html" in response.headers["content-type"] diff --git a/titiler/cmr/settings.py b/titiler/cmr/settings.py index 3d43592..236afac 100644 --- a/titiler/cmr/settings.py +++ b/titiler/cmr/settings.py @@ -11,7 +11,6 @@ class ApiSettings(BaseSettings): cors_origins: str = "*" cachecontrol: str = "public, max-age=3600" root_path: str = "" - debug: bool = False model_config = { "env_prefix": "TITILER_CMR_API_", From 5dedba4f6a555f83dc0af336e63fb195ce9a6260 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Tue, 30 Jan 2024 20:21:36 +0100 Subject: [PATCH 2/3] add permissions --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6089c9e..97dca7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,9 +20,9 @@ on: env: LATEST_PY_VERSION: '3.10' -# permissions: -# id-token: write # This is required for requesting the JWT -# contents: read # This is required for actions/checkout +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout jobs: tests: From 0e44a842c6a65de88bcca5737aa398a6508b6207 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Tue, 30 Jan 2024 20:23:23 +0100 Subject: [PATCH 3/3] move role-arn to secret --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97dca7e..e33ca82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: - role-to-assume: arn:aws:iam::444055461661:role/github-actions-role-eodc + role-to-assume: ${{ secrets.role_arn }} role-session-name: samplerolesession aws-region: us-west-2