137 lines
6.1 KiB
Python
137 lines
6.1 KiB
Python
|
|
from typing import AsyncGenerator
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from httpx import AsyncClient, ASGITransport
|
||
|
|
|
||
|
|
from src.auth.service import get_admin
|
||
|
|
from src.main import app
|
||
|
|
|
||
|
|
PASSWORD="password123"
|
||
|
|
|
||
|
|
AUTH=("tofu", PASSWORD)
|
||
|
|
|
||
|
|
CREATE_INSTANCE_PAYLOAD = {
|
||
|
|
"suppress_deployment": True,
|
||
|
|
"password": PASSWORD,
|
||
|
|
"configuration": {
|
||
|
|
"terraform": {"required_providers": {"random": {"source": "hashicorp/random", "version": ">= 3.0.0"}}},
|
||
|
|
"provider": {"random": {}},
|
||
|
|
"variable": {
|
||
|
|
"password_length": {"description": "Length of the random password", "type": "number", "default": 16}
|
||
|
|
},
|
||
|
|
"resource": {"random_password": {"example": {"length": "${var.password_length}", "special": True}}},
|
||
|
|
"output": {"generated_password": {"value": "${random_password.example.result}", "sensitive": True}},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
INITIAL_STATE_PAYLOAD = {"key": "value"}
|
||
|
|
|
||
|
|
UPDATE_STATE_PAYLOAD = {"key": "value"}
|
||
|
|
|
||
|
|
STATE_LOCK_PAYLOAD_1 = {"ID": "bd812a7e-2297-4b70-acc9-d51015a9172c",
|
||
|
|
"Operation": "OperationTypeInvalid",
|
||
|
|
"Info": "",
|
||
|
|
"Who": "",
|
||
|
|
"Version": "",
|
||
|
|
"Created": "1990-01-01T12:00:00Z",
|
||
|
|
"Path": ""
|
||
|
|
}
|
||
|
|
|
||
|
|
STATE_LOCK_PAYLOAD_2 = {"ID": "ab0eb55f-2f00-4e02-9bf5-e2c6658ab8af",
|
||
|
|
"Operation": "OperationTypeInvalid",
|
||
|
|
"Info": "",
|
||
|
|
"Who": "",
|
||
|
|
"Version": "",
|
||
|
|
"Created": "1990-01-01T12:00:00Z",
|
||
|
|
"Path": ""
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture(scope="module")
|
||
|
|
async def client() -> AsyncGenerator[AsyncClient, None]:
|
||
|
|
host, port = "127.0.0.1", "9000"
|
||
|
|
|
||
|
|
async def override_get_admin():
|
||
|
|
return None
|
||
|
|
|
||
|
|
app.dependency_overrides[get_admin] = override_get_admin
|
||
|
|
async with AsyncClient(transport=ASGITransport(app=app, client=(host, port)), base_url="http://test", auth=AUTH) as client:
|
||
|
|
yield client
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
async def instance_id(client: AsyncClient):
|
||
|
|
response = await client.post("/api/v1/tofu/instances", json=CREATE_INSTANCE_PAYLOAD)
|
||
|
|
assert response.status_code == 202
|
||
|
|
return response.json()["id"]
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.anyio
|
||
|
|
async def test_state_no_locking(client: AsyncClient, instance_id: int):
|
||
|
|
# Initially there should be no state
|
||
|
|
response = await client.get(f"/api/v1/tofu/instances/{instance_id}/state")
|
||
|
|
assert response.status_code == 404
|
||
|
|
# Let's create the state
|
||
|
|
response = await client.post(f"/api/v1/tofu/instances/{instance_id}/state", json=INITIAL_STATE_PAYLOAD)
|
||
|
|
assert response.status_code == 200
|
||
|
|
# Now check the state is retrievable
|
||
|
|
response = await client.get(f"/api/v1/tofu/instances/{instance_id}/state")
|
||
|
|
assert response.status_code == 200
|
||
|
|
assert response.json() == INITIAL_STATE_PAYLOAD
|
||
|
|
# Now update the state
|
||
|
|
response = await client.post(f"/api/v1/tofu/instances/{instance_id}/state", json=UPDATE_STATE_PAYLOAD)
|
||
|
|
assert response.status_code == 200
|
||
|
|
# Now check the state is retrievable
|
||
|
|
response = await client.get(f"/api/v1/tofu/instances/{instance_id}/state")
|
||
|
|
assert response.status_code == 200
|
||
|
|
assert response.json() == UPDATE_STATE_PAYLOAD
|
||
|
|
# Now purge the state
|
||
|
|
response = await client.delete(f"/api/v1/tofu/instances/{instance_id}/state")
|
||
|
|
assert response.status_code == 200
|
||
|
|
# And check it is gone
|
||
|
|
response = await client.get(f"/api/v1/tofu/instances/{instance_id}/state")
|
||
|
|
assert response.status_code == 404
|
||
|
|
|
||
|
|
@pytest.mark.anyio
|
||
|
|
async def test_state_double_locking(client: AsyncClient, instance_id: int):
|
||
|
|
response = await client.request("LOCK", f"/api/v1/tofu/instances/{instance_id}/state", json=STATE_LOCK_PAYLOAD_1)
|
||
|
|
assert response.status_code == 200
|
||
|
|
response = await client.request("LOCK", f"/api/v1/tofu/instances/{instance_id}/state", json=STATE_LOCK_PAYLOAD_2)
|
||
|
|
assert response.status_code == 423
|
||
|
|
assert response.json() == STATE_LOCK_PAYLOAD_1
|
||
|
|
response = await client.request("UNLOCK",
|
||
|
|
f"/api/v1/tofu/instances/{instance_id}/state?ID=" + STATE_LOCK_PAYLOAD_2['ID'])
|
||
|
|
assert response.status_code == 423
|
||
|
|
assert response.json() == STATE_LOCK_PAYLOAD_1
|
||
|
|
response = await client.request("UNLOCK",
|
||
|
|
f"/api/v1/tofu/instances/{instance_id}/state?ID=" + STATE_LOCK_PAYLOAD_1['ID'])
|
||
|
|
assert response.status_code == 200
|
||
|
|
|
||
|
|
@pytest.mark.anyio
|
||
|
|
async def test_state_locked_update(client: AsyncClient, instance_id: int):
|
||
|
|
response = await client.request("LOCK", f"/api/v1/tofu/instances/{instance_id}/state", json=STATE_LOCK_PAYLOAD_1)
|
||
|
|
assert response.status_code == 200
|
||
|
|
response = await client.post(f"/api/v1/tofu/instances/{instance_id}/state?ID=" + STATE_LOCK_PAYLOAD_1['ID'],
|
||
|
|
json=INITIAL_STATE_PAYLOAD)
|
||
|
|
assert response.status_code == 200
|
||
|
|
response = await client.post(f"/api/v1/tofu/instances/{instance_id}/state?ID=" + STATE_LOCK_PAYLOAD_2['ID'],
|
||
|
|
json=UPDATE_STATE_PAYLOAD)
|
||
|
|
assert response.status_code == 423
|
||
|
|
assert response.json() == STATE_LOCK_PAYLOAD_1
|
||
|
|
response = await client.post(f"/api/v1/tofu/instances/{instance_id}/state?ID=" + STATE_LOCK_PAYLOAD_1['ID'],
|
||
|
|
json=UPDATE_STATE_PAYLOAD)
|
||
|
|
assert response.status_code == 200
|
||
|
|
response = await client.delete(f"/api/v1/tofu/instances/{instance_id}/state?ID=" + STATE_LOCK_PAYLOAD_2['ID'])
|
||
|
|
assert response.status_code == 423
|
||
|
|
assert response.json() == STATE_LOCK_PAYLOAD_1
|
||
|
|
response = await client.delete(f"/api/v1/tofu/instances/{instance_id}/state?ID=" + STATE_LOCK_PAYLOAD_1['ID'])
|
||
|
|
assert response.status_code == 200
|
||
|
|
|
||
|
|
@pytest.mark.anyio
|
||
|
|
async def test_state_allow_force_unlock(client: AsyncClient, instance_id: int):
|
||
|
|
# force-unlock doesn't include the ID when calling the unlock endpoint
|
||
|
|
response = await client.request("LOCK", f"/api/v1/tofu/instances/{instance_id}/state", json=STATE_LOCK_PAYLOAD_1)
|
||
|
|
assert response.status_code == 200
|
||
|
|
response = await client.request("UNLOCK", f"/api/v1/tofu/instances/{instance_id}/state", json=STATE_LOCK_PAYLOAD_1)
|
||
|
|
assert response.status_code == 200
|