-
Notifications
You must be signed in to change notification settings - Fork 6
Tests
export DUPLO_HOST=https://your-portal.duplocloud.net
export DUPLO_TOKEN=<your-token>Install with test dependencies:
pip install --editable '.[build,test,aws]'# Unit tests only — fast, no credentials needed
pytest src -m unit
# All integration tests — expensive, slow, creates real cloud resources
pytest src -m integration
# Integration tests for a specific cloud
pytest src -m "integration and aws"
# Integration tests for a specific infra type
pytest src -m "integration and k8s"
pytest src -m "integration and ecs"
# Combine cloud + infra type
pytest src -m "integration and aws and k8s"
# Run only against an existing infra and tenant (skips create/destroy)
pytest src -m integration --infra testk8siso --tenant mytenantnameThese are passed directly to pytest and are defined in conftest.py.
| Argument | Env var fallback | Description |
|---|---|---|
--infra <name> |
DUPLO_INFRA |
Infrastructure name. Create is skipped if it already exists. |
--tenant <name> |
DUPLO_TENANT |
Tenant name. Create is skipped if it already exists. |
Tip: When iterating on a single resource, always pass
--infraand--tenantpointing to something already live so the expensive create/destroy steps are skipped.
infra_name and tenant_name are session fixtures that determine what infrastructure and
tenant the test run targets. Their values are resolved at fixture setup time using the
following rules.
| What you pass | What happens |
|---|---|
--infra foo |
infra = foo (always wins) |
--tenant bar (no --infra), tenant bar exists
|
infra = bar's PlanID (looked up from the portal) |
--tenant bar (no --infra), tenant bar does not exist
|
infra = bar (tenant and infra share the same name) |
DUPLO_TENANT=bar (no CLI args), same lookup as above |
same rules as --tenant bar
|
| nothing given | infra = duploctl{1000–9999} (random, unique per run) |
Once infra_name is known, tenant_name resolves as:
| Priority | Source |
|---|---|
| 1 |
--tenant <name> explicit CLI arg |
| 2 |
DUPLO_TENANT environment variable |
| 3 |
infra_name (DuploCloud convention: tenant = infra name) |
pytest_configure runs before any fixtures and validates consistency when both
--infra and a tenant name are known and differ.
If the resolved tenant already exists on a different infra, pytest exits immediately:
ERROR: Tenant 'mytenant' already exists but belongs to infra 'otheinfra', not 'myinfra'.
Either pass --infra otherinfra to match the tenant's infra,
or choose a --tenant name that does not exist yet.
# Existing infra, same-named tenant (the default convention)
pytest src -m integration --infra testk8siso
# infra_name = "testk8siso", tenant_name = "testk8siso"
# Existing infra, different existing tenant
pytest src -m integration --infra testk8siso --tenant myteam
# infra_name = "testk8siso", tenant_name = "myteam"
# (guard checks that tenant 'myteam' belongs to 'testk8siso')
# Only tenant given — infra is inferred from the tenant's PlanID
pytest src -m integration --tenant myteam
# Looks up 'myteam'; if its PlanID is "testk8siso" → infra_name = "testk8siso"
# Only tenant given — tenant doesn't exist yet (will be created)
pytest src -m integration --tenant newstuff
# infra_name = "newstuff", tenant_name = "newstuff"
# (both will be created from scratch)
# Via env var — same rules as --tenant
export DUPLO_TENANT=myteam
pytest src -m integration
# Fully automatic — random infra+tenant name, full lifecycle
pytest src -m integrationMarkers are combined with -m using boolean expressions (and, or, not).
| Marker | When to use |
|---|---|
unit |
Pure logic tests. No network, no credentials. Always fast. |
integration |
Tests that hit a live DuploCloud portal and create/modify real resources. |
| Marker | Description |
|---|---|
aws |
Tests that are AWS-specific |
gcp |
Tests that are GCP-specific |
azure |
Tests that are Azure-specific |
These mark tests that require a specific kind of infrastructure to be running.
| Marker | Infrastructure | Notes |
|---|---|---|
k8s |
EKS / GKE / AKS | Tests that require a Kubernetes cluster |
ecs |
ECS (AWS only) | Tests for ECS services, task definitions, etc. |
duplo |
Duplo-native | No k8s, no ECS — simplest infra type |
Every test class and every parametrized resource entry carries a marker named after the resource. This lets you zoom in on one resource without listing files manually.
| Marker | Resource |
|---|---|
hosts |
EC2 host lifecycle |
asg |
Auto Scaling Group |
configmap |
Kubernetes ConfigMap |
secret |
Kubernetes Secret |
ingress |
Kubernetes Ingress |
job |
Kubernetes Job |
cronjob |
Kubernetes CronJob |
lambda_ |
AWS Lambda (lambda is a Python keyword — use lambda_ in -m expressions) |
rds |
RDS instance |
cloudfront |
CloudFront distribution |
awssecret |
AWS Secrets Manager secret |
jit |
JIT credentials |
Resource markers compose freely with infra-type markers. Because TestInfra and
TestTenant carry the lifecycle marker, the pattern for focusing on a single
resource is:
(integration and <cloud>) and (<resource> or lifecycle)
# Lambda only
pytest src -m "(integration and aws) and (lambda_ or lifecycle)" --infra testk8siso --tenant testk8siso --no-cov -v
# Lambda + RDS together
pytest src -m "(integration and aws) and (lambda_ or rds or lifecycle)" --infra testk8siso --tenant testk8siso --no-cov -v
# Configmap only (k8s infra)
pytest src -m "(integration and k8s) and (configmap or lifecycle)" --infra testk8siso --tenant testk8siso --no-cov -v
# All aws resources (no lifecycle filter needed — lifecycle is included via `aws`)
pytest src -m "integration and aws" --infra testk8siso --tenant testk8siso --no-cov -v
lifecyclemarker: applied toTestInfraandTestTenant. These classes must be collected for dep registration (create_infra,create_tenant,find_tenant_resource) to work. When filtering by resource marker,or lifecyclepulls them in without requiring you to list test files manually.
Integration tests use two pytest plugins together:
-
pytest-order— enforces global execution order via@pytest.mark.order(N) -
pytest-dependency— skips a test automatically if any named dependency did not pass
Order numbers follow a range-based scheme so each resource group has room to grow without colliding with others. Lower numbers are reserved for foundational lifecycle steps; higher numbers are for per-resource tests; the 990s are for teardown.
| Range | What runs |
|---|---|
| 1 | create infra |
| 2 | list + find infra (read-only validation) |
| 10 | create tenant |
| 11 | list + find tenant |
| 12 | additional tenant reads (users, billing, region, DNS) |
| 20–21 | k8s hosts: create → find (prerequisite node for workloads) |
| 22–26 | AWS hosts: create → find → stop → start → reboot (full EC2 lifecycle) |
| 40–47 | RDS: engine version check → create → find → resize → stop → start → replica create → replica find |
| 50–51 | bulk tenant resources: create → find (parametrized: configmap, cronjob) |
| 90–94 | cloudfront: create → update → list → disable → enable |
| 100–102 | AWS secret: create → find → update |
| 110 | JIT credential tests |
| 120–122 | AI helpdesk: create ticket → variations → send message |
| 993–997 | per-resource deletes |
| 998 | delete tenant |
| 999 | delete infra |
Every resource follows the pattern: create immediately precedes find. The
find step registers a named dependency (name="find_<resource>") that
later tests depend on. This ensures nothing operates on a resource that
wasn't confirmed to exist.
Hosts (20–24) and ASGs (30–34) run before bulk tenant resources (40+)
because they are infrastructure prerequisites — the Kubernetes nodes need to exist
before workloads can be scheduled.
Jobs (75–76) carry depends=["find_asg"] explicitly because a running node is
required for a job to complete.
All create_* tests check if the resource already exists before calling the API.
If it does, the test returns early (passing) rather than failing. This makes it safe
to re-run a test suite against a live environment.
Two fixtures control teardown safety:
-
owns_infra—Falseif--infrawas passed and the infra pre-existed, or if the infra name was inferred from an existing tenant'sPlanID. -
owns_tenant—Falseifowns_infraisFalse, or if the explicit tenant already existed before the run.
The test_find_delete_infra and test_find_delete_tenant tests call pytest.skip()
when ownership is False, so pre-existing infrastructure is never destroyed.
A dependency name (e.g. "find_tenant_resource") is only registered when the test
that declares name= is collected. Always run with a marker that includes the
lifecycle tests (TestInfra, TestTenant, etc.) — they carry the same cloud/infra
markers as the resources they gate. Running only -m k8s without also collecting
these lifecycle classes will cause all downstream deps to warn and skip.
pytest src -m unit# Lambda (AWS) — `or lifecycle` ensures infra/tenant prereqs are collected
pytest src -m "(integration and aws) and (lambda_ or lifecycle)" --infra testk8siso --tenant testk8siso --no-cov -v
# Configmap (k8s)
pytest src -m "(integration and k8s) and (configmap or lifecycle)" --infra testk8siso --tenant testk8siso --no-cov -vpytest src -m "integration and k8s" --infra testk8siso --tenant testk8siso --no-cov -vpytest src -m "integration and aws" -v| Type | Orchestrator | Key resources |
|---|---|---|
k8s |
EKS / GKE / AKS | Services, configmaps, secrets, ingress, cronjobs, jobs |
ecs |
ECS (AWS only) | ECS services, task definitions |
duplo |
None | Hosts, RDS, S3, Lambda, ASG only |
Always pass --infra / --tenant to target an existing environment and avoid
provisioning a new one unnecessarily.
Failed or leftover infras accumulate over time and consume AWS quotas even in a broken state. After any test run that creates an infra, verify cleanup happened:
duploctl infrastructure list --query "[].{Name:Name,Status:ProvisioningStatus}"Any infra stuck in *Failed status should be deleted manually:
duploctl infrastructure delete <name>