Skip to content

test: cover TAG building block inputs end to end - #305

Open
tfelix wants to merge 6 commits into
mainfrom
feature/adds-tag-e2e-test
Open

test: cover TAG building block inputs end to end#305
tfelix wants to merge 6 commits into
mainfrom
feature/adds-tag-e2e-test

Conversation

@tfelix

@tfelix tfelix commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Why

meshStack now lets a building block definition input read a meshStack tag
(assignmentType = TAG), and re-resolves it when the tag value changes or the object it is
read from is reassigned. Nothing in the hub used the feature — assignment_type = "TAG"
appeared zero times — so nothing proved it works the way an end user meets it.

This adds the first e2e coverage, and the TAG input source the NoOp reference building block
was missing.

What the test covers

A TENANT_LEVEL building block reading three tag targets, in one file whose runs share state:

run change asserts
tag_inputs_resolve_on_create all three tags resolve; the landing zone tag carries two values, so a list is really a list
tag_value_changes_resolve_on_the_next_run all three tag values edited the new values resolve
reassigned_payment_method_resolves_its_own_tag no tag edited; the project is funded from the substitute payment method payment_method_tag follows the new source, the other two are untouched

The third case is meshfed's onProjectPaymentMethodChanged, the cheapest of the four
reassignment triggers to exercise.

WORKSPACE is deliberately out of scope: tagging the shared test workspace needs
meshstack_workspace_tag, which the provider's own docs mark "not recommended for general
use" (whole-workspace read-modify-write, races within a single apply).

What it proves, and what it does not

It proves the value a tag holds now reaches the next building block run. It does not prove
the tag edit alone triggered that run. meshStack does trigger one, but the provider exposes no
way to attribute a run to an edit — status.latest_run_uuid exists, but Read never blocks,
so a refresh reads stale outputs. Each run therefore bumps an input the provider does track,
which makes it issue an update and await a run of its own. Both the test file and the skill
amendment say so rather than overclaiming.

Two design points worth a look

The fixture owns everything it tags. There is no meshstack_project_tag or
meshstack_payment_method_tag resource, so a shared fixture project or payment method cannot
be tagged at all — reusing fixtures.<cloud>.mesh_tenant_id would have covered none of the
three targets. A custom platform makes the whole chain reachable with no cloud behind it.

The tenant needs a platform tenant ID from somewhere. The provider counts a tenant as
created once spec.platform_tenant_id is set, and a custom platform has no replicator to set
it. The fixture adds a mandatory manual building block whose only output is assigned
PLATFORM_TENANT_ID — the same approach the provider's own landing zone tests use — so
wait_for_completion = true stays and teardown does not race.

Commits

Ordered for commit-by-commit review; each one is self-consistent and leaves the existing three
noop e2e tests green.

  1. feat: read tag inputs in the noop building block — three nullable list variables, their
    entries in the debug output, the expected-JSON fixture. Nothing wires them up yet.
  2. refactor: lift the noop definition's inputs and outputs into locals — pure move, so the
    next commit reads as "one new definition" rather than a 200-line diff.
  3. feat: add an opt-in tenant-level noop definition with TAG inputs — off unless a consumer
    names the tag keys, so nothing changes for anyone importing the module today.
  4. test: add a self-contained meshTenant fixture for the tag e2e — the whole
    e2e/tag-inputs/ module, no assertions yet, runnable by hand with tofu apply.
  5. test: assert tag inputs resolve on create, on edit and on reassignment
  6. docs: allow one e2e test file to share state when the subject is a change

Verified locally

  • Every module validates against the real provider (v0.25.2, resolved from the registry).
    The provider's TAG validator runs at tofu validate time, which confirms all three targets
    are accepted on a TENANT_LEVEL definition and rejected on WORKSPACE_LEVEL.
  • tofu fmt clean, no trailing whitespace, ci/validate_modules.sh reports only the
    pre-existing missing-pngquant warning.
  • The full scorecard is byte-identical to main for every module.
  • The new test file loads and gets as far as the missing MESHSTACK_ENDPOINT.

Not verified — needs a real run

No live meshStack was available. Four things could still block the first run:

  • Permissions. Creating tag definitions and payment methods needs ADM_TAGDEFINITION_SAVE
    and ADM_PAYMENTMETHOD_SAVE; neither has a workspace-scoped variant. If the smoke-test key
    is not admin-scoped the fix is a permission grant, not a redesign — there is no alternative
    design, per the point about ownership above.
  • Custom-platform tenant replication is proven only against a local meshStack in the
    provider's acceptance tests, not on the dev instance.
  • tag_settle_duration = "180s" is a guess at how long meshStack's own tag-triggered run
    needs to get out of the way. If an update during an in-flight run turns out not to 409, drop
    it to "0s" and save six minutes per run.
  • Mandatory instance tags. The confidentiality/environment locals are copied from
    modules/stackit/project/e2e. A mandatory payment method tag on that instance would need
    adding too.

Also unrelated and left alone: modules/meshstack/noop/backplane/versions.tf uses ~> 4.0 /
~> 7.0 / ~> 0.12, which is the module's pre-existing scorecard failure.

🤖 Generated with Claude Code

tfelix and others added 6 commits September 3, 2026 15:15
The noop building block is the reference implementation of meshStack's
Terraform interface, and TAG inputs are the one input source it did not
cover. Three nullable list variables — one per taggable meshObject a
tenant-level building block can read — plus their entries in the debug
output the e2e tests assert on.

No definition passes them yet, so they resolve to null and the existing
expected fixture just gains three null keys.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A second definition is about to run the same building block, and that
building block's variables have no defaults — so both definitions have to
declare the identical input set. Sharing it is what keeps them in step.

Pure move: `tofu validate` passes and the definition is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A TAG input reads a tag from the workspace, project, payment method or
landing zone the building block belongs to. A workspace-level definition
has no project, so only a tenant-level one reaches the latter three — hence
a second definition rather than three more inputs on the existing one.

It stays off unless a consumer names the tag keys, so nothing changes for
anyone importing the module today. The keys are named rather than
discovered because a definition stores only the key; meshStack resolves the
value per building block.

Needs provider 0.25.2, which is where TAG inputs landed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The three tags under test sit on a project, a payment method and a landing
zone, and every one of them has to be owned by the test: the provider has
no meshstack_project_tag or meshstack_payment_method_tag resource, so a
shared fixture object cannot be tagged at all.

A custom platform makes that chain reachable with no cloud behind it. The
one catch is the tenant: the provider treats it as created once
spec.platform_tenant_id is set, and a custom platform has no replicator to
set it — hence the mandatory manual building block, which is how the
provider's own landing zone tests solve the same problem.

Two payment methods, because reassigning which one funds the project is the
cheapest of meshStack's four tag-source reassignment triggers.

Needs an admin-scoped API key: ADM_TAGDEFINITION_SAVE and
ADM_PAYMENTMETHOD_SAVE have no workspace-scoped variant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three runs sharing one file's state, which is new for this repo: a tag
input can only be seen to follow a tag if the tag changes under a building
block that already exists.

The runs prove that the value a tag holds now reaches the next run. They do
not prove the tag edit alone triggered it — the provider exposes no way to
attribute a run to an edit, so each run bumps an input the provider does
track and waits for a run of its own. The third run edits no tag at all and
still sees a new value, because the project is funded from another payment
method.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ange

The rule against several run blocks in one file gave two concrete reasons,
and neither one applies to a test whose subject is a change to a live
building block's surroundings — a tag it reads moving under it. Spelling
that out is what keeps the exception from being read as permission to share
state in general.

It also records the two things such a file has to get right, both of which
cost a debugging round: bump an input the provider tracks, and let
meshStack's own tag-triggered run get out of the way first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Scorecard Check

Scorecard run on commit 28637185b7ef3a16c3377bff1370500be157d4e0 relative to origin/main

📊 meshstack-hub Module Scorecard

Generated: 2026-09-03 | Modules scanned: 1 | Categories: 7

📋 Per-Module Category Summary

Score per category per building block. n/a = category does not apply to this module.

Module Overall Core Structure Integration AWS Backplane Azure Backplane GCP Backplane STACKIT Backplane Testing
meshstack/noop 🟢 96% 🟢 83% 🟢 100% n/a n/a n/a n/a 🟢 100%

⚠️ 1 module has failing checks — failing categories are expanded below.

Core Structure — some checks failing

Basic module file structure and documentation — applies to 1 modules

Module Score 📦 🔗 📋 📝 🖼️ 📌 🚦 🔒
meshstack/noop 🟢 83%

Core Structure — Summary

Emoji Criterion Coverage Status
📦 buildingblock/ directory exists 1/1 🟢 100%
🔗 meshstack_integration.tf present 1/1 🟢 100%
📋 buildingblock/APP_TEAM_README.md present (no-integration fallback) n/a
📝 buildingblock/README.md with YAML front-matter 1/1 🟢 100%
🖼️ buildingblock/logo.png included 1/1 🟢 100%
📌 buildingblock/versions.tf present 1/1 🟢 100%
🚦 Child meshstack_building_block asserts a SUCCEEDED run n/a
🔒 Provider versions use minimum constraint (>=) 0/1 🔴 0%
Integration — ✅ all passing

meshstack_integration.tf conventions — applies to 1 modules

Module Score 🏷️ 🏢 📤 🔌 📎 🔀 🌱 📋 🏷️ 🧱 📖 📝 📊 🚫 🔄
meshstack/noop 🟢 100%

Integration — Summary

Emoji Criterion Coverage Status
🏷️ variable "hub" in integration 1/1 🟢 100%
🏢 variable "meshstack" in integration 1/1 🟢 100%
📤 building_block_definition output exposed 1/1 🟢 100%
🔌 meshcloud/meshstack in required_providers 1/1 🟢 100%
📎 backplane source uses var.hub.git_ref 1/1 🟢 100%
🔀 ref_name uses var.hub.git_ref 1/1 🟢 100%
🌱 BBD terraform_version >= 1.12.0 1/1 🟢 100%
📋 version_spec.draft uses var.hub.bbd_draft 1/1 🟢 100%
🏷️ BBD metadata.tags forwards var.meshstack.tags 1/1 🟢 100%
🧱 BBD input argument vars with optional() have explicit defaults 1/1 🟢 100%
📖 BBD readme field present 1/1 🟢 100%
📝 BBD readme starts with plain-text description (no heading) 1/1 🟢 100%
📊 BBD readme has shared responsibility table (✅/❌) 1/1 🟢 100%
🚫 No documentation_md output in backplane 1/1 🟢 100%
🔄 meshstack_platform has lifecycle ignore_changes = [availability] n/a
AWS Backplane — not applicable

AWS automation principal conventions (WIF or cross-account StackSet) — applies to 0 modules

No applicable modules.

Azure Backplane — not applicable

Azure UAMI-based automation principal conventions — applies to 0 modules

No applicable modules.

GCP Backplane — not applicable

GCP workload-identity-federation automation principal conventions — applies to 0 modules

No applicable modules.

STACKIT Backplane — not applicable

STACKIT WIF-based automation principal conventions — applies to 0 modules

No applicable modules.

Testing — ✅ all passing

End-to-end test coverage — applies to 1 modules

Module Score ⚙️ 🧪 🚫
meshstack/noop 🟢 100%

Testing — Summary

Emoji Criterion Coverage Status
⚙️ backplane/ directory (optional tier) 1/1 🟢 100%
🧪 e2e/ test directory exists 1/1 🟢 100%
🚫 no .tftest.hcl outside e2e/ 1/1 🟢 100%
e2e/ contains .tftest.hcl files 1/1 🟢 100%

@aws-amplify-eu-central-1

Copy link
Copy Markdown

This pull request is automatically being deployed by Amplify Hosting (learn more).

Access this pull request here: https://pr-305.d1o16zfeoh2slu.amplifyapp.com

no external locking or concurrency group needed. Verify assumptions like this against the OpenTofu
version in use rather than trusting them.

### The exception: a test whose subject *is* a change

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

d: I am not sure how helpful this text below is. I find its rather hard to read and understand. And basically only tells us that an output can change if a tag value somewhere else changes... I can also remove it if you think its nonesense here?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, remove it, it doesn't add much value. you could move such details as README.md to the e2e/tag-inputs folder or split it up as succinct comments for the tf files?

@tfelix
tfelix marked this pull request as ready for review September 3, 2026 15:04

@grubmeshi grubmeshi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general, nice test and i like the scenarios, i think though that we can simplify it and make it more maintainable in the future. see my detailed comments and huddle me in case of questions :D

no external locking or concurrency group needed. Verify assumptions like this against the OpenTofu
version in use rather than trusting them.

### The exception: a test whose subject *is* a change

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, remove it, it doesn't add much value. you could move such details as README.md to the e2e/tag-inputs folder or split it up as succinct comments for the tf files?

Comment on lines 519 to +520
- [ ] One `.tftest.hcl` file per variant, pinning the flag in a file-level `variables` block — never several `run` blocks sharing one file's state
- [ ] …unless the subject of the test *is* a change to a live building block's surroundings — then one file with shared state, and see [The exception](#the-exception-a-test-whose-subject-is-a-change) for the two things it must get right

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f: merge those two into one clear instruction and not into "do this ... unless"
or just relax the requirement of "one tftest.hcl per variant"?


variable "project_tag" {
type = list(string)
default = null

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default = null is redundant afaik, as nullable = true, or is terraform issuing a warning here?

concerning best practice, and to avoid nullability issues, maybe a default = [] is better here?

locals {
# A platform type identifier can never be reused, not even after the type is deleted, so the run
# suffix is what keeps repeated and concurrent runs from colliding. Uppercase and dashes only.
platform_type_name = "NOOP-TAG-${var.test_context.name_suffix}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f: is the name_suffix guranteed to be uppercase? maybe ensure this here using upper here?

}

mandatory_building_block_refs = [
{ uuid = meshstack_building_block_definition.platform_tenant_id.metadata.uuid }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f: i think that should work now (ref all the things!!!):

Suggested change
{ uuid = meshstack_building_block_definition.platform_tenant_id.metadata.uuid }
meshstack_building_block_definition.platform_tenant_id.ref

Comment on lines +19 to +20
condition = contains(["initial", "changed_values", "reassigned_payment_method"], var.scenario)
error_message = "scenario must be one of initial, changed_values or reassigned_payment_method."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f: error_message can be dropped as condition is clear enough? that is similar to refining code comments, btw

Suggested change
condition = contains(["initial", "changed_values", "reassigned_payment_method"], var.scenario)
error_message = "scenario must be one of initial, changed_values or reassigned_payment_method."
condition = contains(["initial", "changed_values", "reassigned_payment_method"], var.scenario)


variable "tag_settle_duration" {
type = string
default = "0s"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
default = "0s"
nullable = false

make the caller think about this input (note that nullable is true by default)

@@ -1,12 +1,15 @@
{
"flag": true,
"landing_zone_tag": null,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f: can be make this somehow non null (by reusing the code from the new scenarios?) it feels it's not a good assertion asserting nothing, in particular all other things here have some dummy value.

project_tag_key = string
payment_method_tag_key = string
landing_zone_tag_key = string
platform_type_name = string

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

d: are we resuing some smoke test platform here? that would be ok, but don't use a platform which happens to be there "by chance" in meshtack-dev.

# Only a tenant-level building block has a project, a payment method and a landing zone to read tags
# from — a workspace-level one can reach workspace tags and nothing else. So the TAG inputs need a
# definition of their own. It runs the same building block; the three inputs are the only difference.
resource "meshstack_building_block_definition" "tenant_tag_inputs" {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd aactually change the existing BBD and extend it with the three things you mention. that improves "coverage" for the already existing noop test run and saves us from having two BBDs, which is also somewhat surprising as "normal" meshstack_integration.tf files have only one BBD targeted at the buildingblock/ folder, which is a design principle I don't want to sacrifice for a test tbh.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants