← Files astronomer-dataARCHIVED FILE

skills/migrating-dagster-to-airflow/scripts/tests/test_inventory.py

6.01 KB · Sep 30, 2026 · 23:17 UTC

↓ Download file

"""Static-scan tests for the redesigned (import-based) inventory.py.

The scanner no longer curates a symbol table: it records any decorator/call whose
origin resolves to a dagster* module, kind = the raw dagster symbol name, and
leaves classification = "pending" for the agent."""

import inventory


def _scan(root):
    return inventory.build_manifest(root, use_runtime=False)


def _by_kind(records, kind):
    return [r for r in records if r["kind"] == kind]


def _names(records, kind):
    return sorted(r["name"] for r in records if r["kind"] == kind)


def test_import_based_detection_kinds(synth_project):
    m = _scan(synth_project)
    recs = list(m["units"].values())
    assert m["modes"] == ["static"]
    # kinds are the RAW dagster symbol names (no internal remapping)
    assert {"daily_sales", "raw_sales", "local_only_asset"} <= set(
        _names(recs, "asset")
    )
    assert _names(recs, "multi_asset") == ["multi_outs", "multi_specs"]
    assert _by_kind(recs, "op") and _by_kind(recs, "define_asset_job")
    assert _by_kind(recs, "component_instance")


def test_classification_is_pending_everywhere(synth_project):
    # the scanner stops proposing MECH/JUDG/REDESIGN/NONE; the agent classifies
    m = _scan(synth_project)
    assert all(r["classification"] == "pending" for r in list(m["units"].values()))
    assert set(m["legend"]) >= {"MECH", "JUDG", "REDESIGN", "NONE", "pending"}


def test_counts_are_per_kind(synth_project):
    m = _scan(synth_project)
    assert m["counts"].get("asset", 0) >= 3
    assert m["total"] == len(list(m["units"].values()))


def test_bare_from_import_decorator(synth_project):
    # `from dagster import asset` then `@asset` resolves to kind "asset"
    m = _scan(synth_project)
    assert "raw_sales" in _names(list(m["units"].values()), "asset")


def test_module_alias_decorator(synth_project):
    # `import dagster as dg` then `@dg.asset` resolves to kind "asset"
    m = _scan(synth_project)
    assert "local_only_asset" in _names(list(m["units"].values()), "asset")


def test_aliased_decorator_import(synth_project):
    # `from dagster import sensor as sensor_decorator` then `@sensor_decorator`
    # must still resolve to the dagster symbol `sensor` (the old blind spot)
    m = _scan(synth_project)
    assert "the_real_sensor_name" in _names(list(m["units"].values()), "sensor")


def test_integration_package_origin(synth_project):
    # `from dagster_dbt import dbt_cloud_assets` resolves (dagster_* origin)
    m = _scan(synth_project)
    assert "cloud_models" in _names(list(m["units"].values()), "dbt_cloud_assets")


def test_deprecated_spelling_flagged(synth_project):
    # DEPRECATED_SYMBOLS (the stable past) still flags spelling + note
    m = _scan(synth_project)
    solids = _by_kind(list(m["units"].values()), "solid")
    assert len(solids) == 1 and solids[0]["spelling"] == "deprecated"
    assert solids[0]["note"]


def test_declared_name_prefers_kwarg_over_binding(synth_project):
    m = _scan(synth_project)
    assert "the_real_job_name" in _names(list(m["units"].values()), "define_asset_job")
    assert "the_real_sensor_name" in _names(list(m["units"].values()), "sensor")


def test_custom_io_manager_subclass_chain(synth_project):
    m = _scan(synth_project)
    io = {r["name"] for r in _by_kind(list(m["units"].values()), "io_manager")}
    assert {"BaseParquetIO", "DerivedParquetIO"} <= io


def test_resource_dict_key_naming(synth_project):
    # a dagster IO-manager constructed as a resources-dict value is named by key
    m = _scan(synth_project)
    assert "warehouse" in _names(list(m["units"].values()), "DuckDBPandasIOManager")


def test_multi_asset_outputs_are_fields(synth_project):
    m = _scan(synth_project)
    outs = {
        r["name"]: [o["name"] for o in r.get("outputs", [])]
        for r in _by_kind(list(m["units"].values()), "multi_asset")
    }
    assert outs["multi_outs"] == ["out_a", "out_b"]
    # output names are not minted as standalone asset units
    assert not ({"out_a", "out_b"} & set(_names(list(m["units"].values()), "asset")))


def test_signature_edges_and_producer_io_manager(synth_project):
    m = _scan(synth_project)
    daily = next(r for r in list(m["units"].values()) if r["name"] == "daily_sales")
    e = daily["source_edges"][0]
    assert e["upstream"] == "raw_sales" and e.get("from") == "signature"
    assert e["io_manager"] == "io_manager" and e["io_manager_source"] == "producer"
    assert e["consumer_io_manager"] == "warehouse"


def test_both_conditional_branches_visible(synth_project):
    m = _scan(synth_project)
    by_name = {r["name"]: r for r in list(m["units"].values())}
    assert "cloud_models" in by_name and "local_only_asset" in by_name
    assert isinstance(by_name["cloud_models"].get("conditional"), str)
    assert "not (" in by_name["local_only_asset"].get("conditional", "")


def test_multidoc_component_yaml(synth_project):
    m = _scan(synth_project)
    assert _names(list(m["units"].values()), "component_instance") == [
        "my_lib.ComponentA",
        "my_lib.ComponentB",
    ]


def test_dagster_cloud_ref_and_coupling_field(synth_project):
    m = _scan(synth_project)
    assert _by_kind(list(m["units"].values()), "branch_env_ref")
    assert m["dbt_manifest_coupling"] == []


def test_freshness_policy_both_spellings(synth_project):
    # import-based: kind is the raw symbol; the agent classifies legacy vs GA
    m = _scan(synth_project)
    kinds = {r["kind"] for r in list(m["units"].values())}
    assert {"FreshnessPolicy", "LegacyFreshnessPolicy"} <= kinds


def test_helper_calls_not_recorded(synth_project):
    # AssetOut inside outs=, EnvVar defaults, etc. are not definition sites
    m = _scan(synth_project)
    kinds = {r["kind"] for r in list(m["units"].values())}
    assert not (kinds & {"AssetOut", "AssetSpec", "AssetIn", "get_dagster_logger"})


def test_units_shape_for_downstream_scripts(synth_project):
    m = _scan(synth_project)
    assert m["units"] and all(":" in uid for uid in m["units"])
    assert all(
        "source_edges" in rec and "edges" not in rec for rec in m["units"].values()
    )

SHA-256: a904fc2594fb2d9848926c76ef0a755300fb8f7ce28015273c358370ab5ac57b