functional-tests

Gunakan saat menulis, mengedit, meninjau, atau menjalankan pengujian fungsional (end-to-end) untuk repositori Astronomer APC. Mencakup penyiapan skenario, pola testinfra,…

npx skills add https://github.com/astronomer/astronomer --skill functional-tests

Functional Test Writing Guide

Overview

Functional tests run against a live Kubernetes cluster (kind) with the Helm chart installed. Unlike chart tests, they verify real runtime behavior: running processes, user identity, network reachability, and configuration values.


Critical Rules

  1. Always run tests with uv run — never python3 -m pytest or python -m pytest
  2. Pass --topology to bin/reset-local-dev (unified/control/data) before running — pytest itself needs no env var, it infers topology from the test's own location on disk
  3. Always use uppercase kubeconfig constants from tests.utils.k8sKUBECONFIG_UNIFIED, KUBECONFIG_CONTROL, KUBECONFIG_DATA (not lowercase variants)
  4. Run bin/reset-local-dev before the first test run to set up the cluster

Installation Scenarios

Three scenarios exist, each with its own test directory:

ScenarioDirectoryDescription
unifiedtests/functional/unified/Control plane + data plane in one cluster
controltests/functional/control/Control plane components only
datatests/functional/data/Data plane components only

Cross-scenario tests (applicable to all planes) belong in tests/functional/shared/. This directory does not yet exist — create it (with an __init__.py) when adding the first shared test, then add an entry point to each scenario's conftest if needed.


Local Setup Workflow

# 1. Set up the cluster for your chosen topology (downloads tools, generates certs, launches kind, installs chart)
bin/reset-local-dev --topology=unified   # or: control, data

# 2. Run the tests (does NOT tear down the cluster — re-run freely while iterating)
uv run pytest tests/functional/unified

# 3. See helper file paths (kubeconfig, etc.)
make show-test-helper-files

Makefile shortcuts run setup + tests in one step:

make test-functional-unified
make test-functional-control
make test-functional-data

Enable verbose debug output (helm install --debug, kubectl -v=9):

export DEBUG=1

Stored artifacts (outside the repo, consistent across runs):

  • Tools: ~/.local/share/astronomer-software/bin
  • Kubeconfigs: ~/.local/share/astronomer-software/kubeconfig/{unified,control,data}
  • Certs: ~/.local/share/astronomer-software/certs (auto-renewed if expiring within 4 weeks)

Test Organization

tests/functional/
├── conftest.py                        # Shared fixtures (k8s clients, named pod hosts)
├── unified/
│   ├── conftest.py                    # unified-specific fixtures (if any)
│   ├── test_config.py                 # Configuration and behavior assertions
│   ├── test_container_user_is_not_root.py
│   ├── test_network_security.py       # Port-scan test (complex one-off, do not replicate pattern)
│   └── test_container_read_only_root.py
├── control/
│   ├── conftest.py
│   ├── test_control.py
│   ├── test_pod_configs.py
│   └── test_container_user_is_not_root.py
├── data/
│   ├── test_data.py
│   └── test_container_user_is_not_root.py
└── shared/                            # Create when adding first cross-scenario test
    ├── __init__.py
    └── test_<name>.py

Kubeconfig Helpers

Always import from tests.utils.k8s:

from tests.utils.k8s import KUBECONFIG_UNIFIED, KUBECONFIG_CONTROL, KUBECONFIG_DATA

These resolve to ~/.local/share/astronomer-software/kubeconfig/<scenario>.

Known bug: tests/functional/control/test_container_user_is_not_root.py imports kubeconfig_control (lowercase), which does not exist in tests.utils.k8s. Fix this to KUBECONFIG_CONTROL whenever you touch that file.


Shared Fixtures

tests/functional/conftest.py provides these fixtures (all scope="function"):

FixtureTypeDescription
k8s_core_v1_clientCoreV1ApiKubernetes core/v1 API client
k8s_apps_v1_clientAppsV1ApiKubernetes apps/v1 API client
cp_nginxtestinfra.Hostcp-ingress-controller nginx container
dp_nginxtestinfra.Hostdp-ingress-controller nginx container
grafanatestinfra.Hostgrafana container
houston_apitestinfra.Hosthouston container
prometheustestinfra.Hostprometheus-0 container
es_mastertestinfra.Hostelasticsearch-master-0 container
es_datatestinfra.Hostelasticsearch-data-0 container
all_containerslist[testinfra.Host]Every container in the astronomer namespace

Writing Tests

Assert command output in a container

def test_prometheus_user(prometheus):
    user = prometheus.check_output("whoami")
    assert user == "nobody", f"Expected 'nobody', got '{user}'"

Assert a file exists and has expected content

def test_dashboard_config_mounted(grafana):
    f = grafana.file("/etc/grafana/provisioning/dashboards/dashboard.yaml")
    assert f.exists
    assert f.is_file
    content = grafana.check_output("cat /etc/grafana/provisioning/dashboards/dashboard.yaml")
    assert "apiVersion: 1" in content
    assert "providers:" in content

Assert containers do not run as root

import pytest
import testinfra
from tests.utils.k8s import KUBECONFIG_UNIFIED, get_pod_running_containers

container_ignore_list = ["kube-state", "houston", "astro-ui"]

def test_container_user_is_not_root():
    containers = get_pod_running_containers(kubeconfig=KUBECONFIG_UNIFIED, namespace="astronomer")
    for container in containers.values():
        if container["_name"] in container_ignore_list:
            pytest.skip(f"Unsupported container: {container['_name']}")
        host = testinfra.get_host(
            f"kubectl://{container['pod_name']}?container={container['_name']}&namespace={container['namespace']}",
            kubeconfig=KUBECONFIG_UNIFIED,
        )
        user = host.user()
        assert user.name != "root"
        assert user.uid != 0
        assert user.gid != 0

Use the Kubernetes API directly

def test_ensure_feature_disabled(k8s_core_v1_client):
    pods = k8s_core_v1_client.list_namespaced_pod("astronomer")
    should_not_run = ["prometheus-postgres-exporter"]
    for pod in pods.items:
        for feature in should_not_run:
            if feature in pod.metadata.name:
                raise ValueError(f"Expected '{feature}' to be disabled")

Parse JSON config from a container process

import json

def test_houston_config(houston_api):
    data = houston_api.check_output(
        "echo \"config = require('config'); console.log(JSON.stringify(config))\" | node -"
    )
    config = json.loads(data)
    assert "url" not in config["nats"]
    assert len(config["nats"]["servers"]) > 0

Flaky Tests

Use @pytest.mark.flaky for tests that depend on eventually-consistent cluster state (e.g. network reachability, pod readiness):

@pytest.mark.flaky(reruns=20, reruns_delay=10)
def test_houston_can_reach_prometheus(houston_api):
    assert houston_api.check_output(
        "wget --timeout=5 -qO- http://astronomer-prometheus.astronomer.svc.cluster.local:9090/targets"
    )
  • reruns: max retry attempts on failure
  • reruns_delay: seconds between retries
  • Use sparingly — only when the cluster genuinely needs time to converge

Utility Functions

From tests.utils.k8s:

get_pod_running_containers(namespace, kubeconfig=None) -> dict Returns {pod_name_container_name: container_info} for all ready containers. Each value includes pod_name, namespace, and _name (container name).

get_pod_by_label_selector(namespace, label_selector, kubeconfig) -> str Returns the name of the first pod matching the given label selector. Asserts at least one pod is found.


What NOT to Do

  • Do not hardcode kubeconfig paths — always use the constants from tests.utils.k8s
  • Do not run with python -m pytest — always use uv run pytest
  • Do not replicate the class-based structure of test_network_security.py for ordinary tests — that file is a one-off for a specialized port-scan workflow
  • Do not add tests directly to tests/functional/ root — tests belong in a scenario subdirectory or shared/

Lebih banyak skill dari astronomer

airflow
astronomer
Kueri, kelola, dan pecahkan masalah DAG, proses, tugas, serta konfigurasi sistem Apache Airflow. Mendukung 30+ perintah untuk inspeksi DAG, manajemen proses, pencatatan tugas, kueri konfigurasi, dan akses langsung REST API. Kelola beberapa instance Airflow dengan konfigurasi persisten; temukan secara otomatis deployment lokal dan Astro. Jalankan proses DAG secara sinkron (tunggu hingga selesai) atau asinkron, diagnosis kegagalan, hapus proses untuk percobaan ulang, dan akses log tugas dengan filter percobaan ulang/indeks peta. Keluaran...
official
airflow-hitl
astronomer
Gerbang persetujuan manusia, input formulir, dan percabangan dalam DAG Airflow menggunakan operator yang dapat ditunda. Empat jenis operator: ApprovalOperator untuk keputusan setuju/tolak, HITLOperator untuk pemilihan multi-opsi dengan formulir, HITLBranchOperator untuk perutean tugas yang digerakkan manusia, dan HITLEntryOperator untuk pengumpulan data formulir. Semua operator dapat ditunda, membebaskan slot pekerja sambil menunggu respons manusia melalui tab Required Actions di UI Airflow atau REST API. Mendukung fitur opsional termasuk kustom...
official
airflow-state-store
astronomer
Persists task and asset state across retries and DAG runs using Airflow 3.3's AIP-103 key/value stores (`task_state_store`, `asset_state_store`) and the…
official
analyzing-data
astronomer
Kueri gudang data Anda untuk menjawab pertanyaan bisnis dengan pola yang di-cache dan pemetaan konsep. Mendukung pencarian pola dan caching untuk jenis pertanyaan berulang, dengan pencatatan hasil untuk meningkatkan kueri di masa mendatang. Menyertakan cache pemetaan konsep-ke-tabel dan penemuan skema tabel melalui INFORMATION_SCHEMA atau grep basis kode. Menyediakan fungsi kernel run_sql() dan run_sql_pandas() yang mengembalikan DataFrame Polars atau Pandas untuk analisis. Perintah CLI untuk mengelola cache konsep, pola, dan tabel, plus...
official
annotating-task-lineage
astronomer
Anotasi tugas Airflow dengan lineage data menggunakan inlet dan outlet. Mendukung objek Dataset OpenLineage, Aset Airflow, dan Dataset Airflow untuk mendefinisikan input dan output di seluruh basis data, gudang data, dan penyimpanan cloud. Digunakan sebagai cadangan ketika operator tidak memiliki ekstraktor OpenLineage bawaan; mengikuti sistem prioritas empat tingkat di mana ekstraktor kustom dan metode OpenLineage diutamakan. Menyertakan pembantu penamaan dataset untuk Snowflake, BigQuery, S3, dan PostgreSQL guna memastikan konsistensi...
official
authoring-dags
astronomer
Panduan kerja untuk membuat DAG Apache Airflow dengan integrasi validasi dan pengujian. Pendekatan enam fase terstruktur: temukan lingkungan dan pola yang ada, rencanakan struktur DAG, implementasikan sesuai praktik terbaik, validasi dengan perintah CLI af, uji dengan persetujuan pengguna, dan lakukan iterasi perbaikan. Perintah CLI untuk penemuan (af config connections, af config providers, af dags list) dan validasi (af dags errors, af dags get, af dags explore) memberikan umpan balik langsung pada DAG...
official
authoring-go-sdk-tasks
astronomer
Writes Airflow task logic in Go using the Airflow Go SDK. Use when the user wants to implement Airflow tasks in Go, asks about `BundleProvider`/`RegisterDags`,…
official
authoring-java-sdk-tasks
astronomer
Menulis logika tugas Airflow dalam Java, Kotlin, atau bahasa JVM lainnya menggunakan Airflow Java SDK. Gunakan saat pengguna ingin mengimplementasikan tugas Airflow dalam Java/JVM, meminta…
official