Skip to content
Kelly Ferrone edited this page Mar 9, 2026 · 2 revisions

Running Tests

Prerequisites

export DUPLO_HOST=https://your-portal.duplocloud.net
export DUPLO_TOKEN=<your-token>

Install with test dependencies:

pip install --editable '.[build,test,aws]'

Quick Reference

# 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 mytenantname

CLI Arguments

These 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 --infra and --tenant pointing to something already live so the expensive create/destroy steps are skipped.


Name Resolution

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.

infra_name resolution

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)

tenant_name resolution

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)

Safety guard

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.

Common scenarios

# 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 integration

Markers

Markers are combined with -m using boolean expressions (and, or, not).

Test type

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.

Cloud provider

Marker Description
aws Tests that are AWS-specific
gcp Tests that are GCP-specific
azure Tests that are Azure-specific

Infrastructure type

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

Resource markers

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

lifecycle marker: applied to TestInfra and TestTenant. These classes must be collected for dep registration (create_infra, create_tenant, find_tenant_resource) to work. When filtering by resource marker, or lifecycle pulls them in without requiring you to list test files manually.


Dependencies & Ordering

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

Ordering scheme

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

Create-then-find pairs

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.

Compute-first ordering

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.

Idempotent create / ownership guards

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_infraFalse if --infra was passed and the infra pre-existed, or if the infra name was inferred from an existing tenant's PlanID.
  • owns_tenantFalse if owns_infra is False, 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.

Dependency registration

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.


Example Workflows

Unit tests only — fast, no credentials

pytest src -m unit

Zoom in on a single resource

# 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 -v

All k8s resources against an existing infra

pytest src -m "integration and k8s" --infra testk8siso --tenant testk8siso --no-cov -v

Full AWS run (expensive — creates real resources)

pytest src -m "integration and aws" -v

Infra Types

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.

Notes on Cleanup

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>

Clone this wiki locally