diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..9eb99b1 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,75 @@ +# Generated by seam-plop +name: Python Test +on: + push: + workflow_dispatch: + inputs: + sdkSha: + description: "SHA of the seamapi-python commit to run against" + type: string + required: false + connectSha: + description: "SHA of the seam-connect commit to run against" + type: string + required: false + prRepo: + description: "Repository of PR context (needed when leaving comments)" + type: string + required: false + prNumber: + description: "PR number (needed when leaving comments)" + type: string + +jobs: + test: + runs-on: ubuntu-latest + env: + CONTAINER_REGISTRY: "registry.digitalocean.com" + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.sdkSha }} + - name: Login to DO container registry + uses: docker/login-action@v1 + with: + registry: ${{ env.CONTAINER_REGISTRY }} + username: ${{ secrets.DIGITAL_OCEAN_TOKEN }} + password: ${{ secrets.DIGITAL_OCEAN_TOKEN }} + - name: Pre-pull Seam Connect image + run: docker pull ${{ env.CONTAINER_REGISTRY }}/seam/seam-connect:${{ github.event.inputs.connectSha || 'latest' }} + - uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install Poetry + uses: snok/install-poetry@v1 + - if: ${{ github.event.inputs.prNumber }} + uses: marocchino/sticky-pull-request-comment@v2 + with: + number: ${{ github.event.inputs.prNumber }} + repo: ${{ github.event.inputs.prRepo }} + GITHUB_TOKEN: ${{ secrets.BOT_GH_TOKEN }} + message: | + ⌛️ [running Python SDK tests](${{ env.RUN_URL }})... + - run: poetry install + - run: poetry run pytest -s + - name: Comment on failure + if: ${{ github.event.inputs.prNumber && failure() }} + uses: marocchino/sticky-pull-request-comment@v2 + with: + number: ${{ github.event.inputs.prNumber }} + repo: ${{ github.event.inputs.prRepo }} + GITHUB_TOKEN: ${{ secrets.BOT_GH_TOKEN }} + message: | + ⛔️ [Python SDK tests failed](${{ env.RUN_URL }}). + + - name: Comment on success + if: ${{ github.event.inputs.prNumber && success() }} + uses: marocchino/sticky-pull-request-comment@v2 + with: + number: ${{ github.event.inputs.prNumber }} + repo: ${{ github.event.inputs.prRepo }} + GITHUB_TOKEN: ${{ secrets.BOT_GH_TOKEN }} + message: | + ✅ [Python SDK tests passed](${{ env.RUN_URL }}). diff --git a/README.md b/README.md index 071d68e..0a43e04 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ seam.locks.lock_door(some_lock) This project uses [poetry](https://github.com/python-poetry/poetry) - To setup the project and install dependencies run `poetry install` -- To run tests, run `poetry run pytest` +- To run tests, run `poetry run pytest -s` - To build the project for publishing, run `poetry build` Commits to `main` following [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) will automatically be published to PyPI. diff --git a/seamapi/access_codes.py b/seamapi/access_codes.py index a0151ac..54d67f9 100644 --- a/seamapi/access_codes.py +++ b/seamapi/access_codes.py @@ -231,7 +231,6 @@ def update( success_res: Any = action_attempt.result return AccessCode.from_dict(success_res["access_code"]) - def delete( self, access_code: Union[AccessCodeId, AccessCode], diff --git a/seamapi/connect_webviews.py b/seamapi/connect_webviews.py index 980546c..4850a7c 100644 --- a/seamapi/connect_webviews.py +++ b/seamapi/connect_webviews.py @@ -59,7 +59,7 @@ def list(self) -> List[ConnectWebview]: A list of connect webviews """ - res = requests.post( + res = requests.get( f"{self.seam.api_url}/connect_webviews/list", headers={"Authorization": f"Bearer {self.seam.api_key}"}, ) diff --git a/seamapi/connected_accounts.py b/seamapi/connected_accounts.py index 7c01ab4..eb0bd7f 100644 --- a/seamapi/connected_accounts.py +++ b/seamapi/connected_accounts.py @@ -55,7 +55,7 @@ def list(self) -> List[ConnectedAccount]: A list of connected accounts. """ - res = requests.post( + res = requests.get( f"{self.seam.api_url}/connected_accounts/list", headers={"Authorization": f"Bearer {self.seam.api_key}"}, ) diff --git a/seamapi/types.py b/seamapi/types.py index 1d2b5e5..b254f9a 100644 --- a/seamapi/types.py +++ b/seamapi/types.py @@ -88,6 +88,7 @@ class AccessCode: starts_at: Optional[str] = None ends_at: Optional[str] = None name: Optional[str] = "" + status: Optional[str] = None class AbstractActionAttempts(abc.ABC): @@ -149,6 +150,7 @@ def update( code: Optional[str] = None, starts_at: Optional[str] = None, ends_at: Optional[str] = None, + status: Optional[str] = None, ) -> AccessCode: raise NotImplementedError diff --git a/seamapi/workspaces.py b/seamapi/workspaces.py index d482ee0..25d9bb5 100644 --- a/seamapi/workspaces.py +++ b/seamapi/workspaces.py @@ -67,7 +67,7 @@ def list( Workspace """ - workspace_id = to_workspace_id(workspace) + workspace_id = None if workspace is None else to_workspace_id(workspace) res = requests.get( f"{self.seam.api_url}/workspaces/list", params={"workspace_id": workspace_id}, @@ -102,8 +102,7 @@ def get( ------ Workspace """ - - workspace_id = to_workspace_id(workspace) + workspace_id = None if workspace is None else to_workspace_id(workspace) res = requests.get( f"{self.seam.api_url}/workspaces/get", params={"workspace_id": workspace_id}, diff --git a/tests/access_codes/test_access_codes.py b/tests/access_codes/test_access_codes.py index bd317b4..162d585 100644 --- a/tests/access_codes/test_access_codes.py +++ b/tests/access_codes/test_access_codes.py @@ -1,14 +1,17 @@ from seamapi import Seam -from tests.fixtures.login_via_schlage import login_via_schlage +from tests.fixtures.run_august_factory import run_august_factory def test_access_codes(seam: Seam): - login_via_schlage(seam) + run_august_factory(seam) some_device = seam.devices.list()[0] - created_access_code = seam.access_codes.create(some_device.device_id, "Test code", "4444") + created_access_code = seam.access_codes.create( + some_device.device_id, "Test code", "4444" + ) assert created_access_code.name == "Test code" + assert created_access_code.status == "setting" access_codes = seam.access_codes.list(some_device.device_id) assert len(access_codes) == 1 diff --git a/tests/action_attempts/test_action_attempts.py b/tests/action_attempts/test_action_attempts.py index f0374d1..9fba95f 100644 --- a/tests/action_attempts/test_action_attempts.py +++ b/tests/action_attempts/test_action_attempts.py @@ -1,12 +1,14 @@ from seamapi import Seam -from tests.fixtures.login_via_schlage import login_via_schlage +from tests.fixtures.run_august_factory import run_august_factory def test_action_attempts(seam: Seam): - login_via_schlage(seam) + run_august_factory(seam) some_device = seam.devices.list()[0] - created_access_code = seam.access_codes.create(some_device.device_id, "Test code", "4444") + created_access_code = seam.access_codes.create( + some_device.device_id, "Test code", "4444" + ) delete_action_attempt = seam.access_codes.delete(created_access_code) action_attempt = seam.action_attempts.get(delete_action_attempt) diff --git a/tests/conftest.py b/tests/conftest.py index 817cbe6..0972f8d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,17 +22,21 @@ class SeamBackend: sandbox_api_key: str -@pytest.fixture(scope="session") +# TODO this should use scope="session", but there's some issue, this would +# dramatically reduce test time to switch +@pytest.fixture(scope="function") def seam_backend(): with PostgresContainer("postgres:13", dbname="postgres") as pg: db_host = "host.docker.internal" if sys.platform == "darwin" else "172.17.0.1" - db_url = f"postgresql://test:test@{db_host}:{pg.get_exposed_port(pg.port_to_expose)}/seam_api" - with DockerContainer("seam-connect").with_env( + db_url = f"postgresql://test:test@{db_host}:{pg.get_exposed_port(pg.port_to_expose)}/postgres" + with DockerContainer("registry.digitalocean.com/seam/seam-connect").with_env( "DATABASE_URL", # TODO on mac us docker.host.internal instead of 172.17.0.1 when someone # with a mac needs to run tests db_url, - ).with_env("DATABASE_NAME", "seam_api").with_env("NODE_ENV", "test").with_env( + ).with_env("POSTGRES_DATABASE", "postgres").with_env( + "NODE_ENV", "test" + ).with_env( "POSTGRES_HOST", db_host ).with_env( "SERVER_BASE_URL", "http://localhost:3020" diff --git a/tests/connected_accounts/test_connected_accounts.py b/tests/connected_accounts/test_connected_accounts.py index 197c2ad..389a236 100644 --- a/tests/connected_accounts/test_connected_accounts.py +++ b/tests/connected_accounts/test_connected_accounts.py @@ -1,9 +1,9 @@ from seamapi import Seam -from tests.fixtures.login_via_schlage import login_via_schlage +from tests.fixtures.run_august_factory import run_august_factory def test_connected_accounts(seam: Seam): - login_via_schlage(seam) + run_august_factory(seam) connected_accounts = seam.connected_accounts.list() assert len(connected_accounts) > 0 diff --git a/tests/devices/test_devices.py b/tests/devices/test_devices.py index 1c80600..c977b1e 100644 --- a/tests/devices/test_devices.py +++ b/tests/devices/test_devices.py @@ -1,16 +1,16 @@ from seamapi import Seam -from tests.fixtures.login_via_schlage import login_via_schlage +from tests.fixtures.run_august_factory import run_august_factory from seamapi.utils.deep_attr_dict import DeepAttrDict def test_devices(seam: Seam): - login_via_schlage(seam) + run_august_factory(seam) devices = seam.devices.list() assert len(devices) > 0 - some_device = seam.devices.get(name="FRONT DOOR") - assert some_device.properties.name == "FRONT DOOR" + some_device = seam.devices.get(name="Generated Lock 0") + assert some_device.properties.name == "Generated Lock 0" locks = seam.locks.list() assert len(locks) > 0 @@ -18,11 +18,12 @@ def test_devices(seam: Seam): some_lock = seam.locks.get(device=(some_device)) assert some_lock.device_id == some_device.device_id - assert some_lock.properties.locked == False - seam.locks.lock_door(device=(some_device)) - some_locked_lock = seam.locks.get(device=(some_device)) - assert some_locked_lock.properties.locked == True + assert some_lock.properties.locked == True seam.locks.unlock_door(device=(some_device.device_id)) some_unlocked_lock = seam.locks.get(device=(some_device)) assert some_unlocked_lock.properties.locked == False + + seam.locks.lock_door(device=(some_device)) + some_locked_lock = seam.locks.get(device=(some_device)) + assert some_locked_lock.properties.locked == True diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py index e0ccd91..b194b86 100644 --- a/tests/fixtures/__init__.py +++ b/tests/fixtures/__init__.py @@ -1 +1 @@ -from .login_via_schlage import login_via_schlage +from .run_august_factory import run_august_factory diff --git a/tests/fixtures/login_via_schlage.py b/tests/fixtures/login_via_schlage.py deleted file mode 100644 index d1d9dcc..0000000 --- a/tests/fixtures/login_via_schlage.py +++ /dev/null @@ -1,22 +0,0 @@ -from seamapi import Seam -import time -import requests - - -def login_via_schlage(seam: Seam): - webview = seam.connect_webviews.create(accepted_providers=["schlage"]) - - # This is an internal endpoint that will be removed, don't use it, see how - # it says "internal" there? It's not going to stick around. - schlage_login_res = requests.post( - f"{seam.api_url}/internal/schlage/login", - json={ - "email": "jane@example.com", - "password": "1234", - "connect_webview_id": webview.connect_webview_id, - }, - ).json() - - # We've completed a webview login, which will load devices into this - # workspace - pass diff --git a/tests/fixtures/run_august_factory.py b/tests/fixtures/run_august_factory.py new file mode 100644 index 0000000..6ff75cb --- /dev/null +++ b/tests/fixtures/run_august_factory.py @@ -0,0 +1,17 @@ +from seamapi import Seam +import time +import requests + + +def run_august_factory(seam: Seam): + factory_res = requests.post( + f"{seam.api_url}/internal/scenarios/factories/load", + json={ + "factory_name": "create_august_devices", + "input": {"num": 1}, + "sync": True, + }, + headers={ + "Authorization": f"Bearer {seam.api_key}", + }, + ) diff --git a/tests/workspaces/test_workspaces.py b/tests/workspaces/test_workspaces.py index 8781fe0..d34e39b 100644 --- a/tests/workspaces/test_workspaces.py +++ b/tests/workspaces/test_workspaces.py @@ -1,9 +1,9 @@ from seamapi import Seam -from tests.fixtures.login_via_schlage import login_via_schlage +from tests.fixtures.run_august_factory import run_august_factory def test_workspaces(seam: Seam): - login_via_schlage(seam) + run_august_factory(seam) ws = seam.workspaces.get() assert ws.is_sandbox == True