migrate-json-golden-tests
Skill916 starsChanged 9 days agoMITView on GitHub
**WORKFLOW SKILL** — Migrate JSON-based golden file test scenarios (TestGolden) to programmatic Go unit tests in the pipeline package. USE FOR: converting testdata/<GroupName>/*.json scenarios into Go tests that construct types with astmodel and run specific pipeline stages. DO NOT USE FOR: adding new test scenarios from scratch, fixing existing Go unit tests, or code review.
- Deletes or force-pushes
In this repository8 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .github/copilot-instructions.mdCopilot instructions
- create-new-resourceSkill
- create-release-notesSkill
- create-releaseSkill
- diagnosing-vcr-failuresSkill
- migrate-group-versioningSkill
- testing-aso-recordingsSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
16 rules. About 0 are common, 11 are distinctive, and 5 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈11
Distinctive · estimate
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 40
Read config.yaml in the same directory to understand test configuration:
- Distinctive: line 43
Read each JSON file to understand what it tests:
- Very specific: line 55
Look at the config.yaml to understand what features the scenarios exercise (e.g., hasArmResources: true suggests ARM type creation is …
- Distinctive: line 57
Place tests in the test file for that stage:
- Distinctive: line 65
Before creating new tests, check the target test file for tests that already cover the same scenarios:
- Distinctive: line 67
Read the target test file completely.
- Distinctive: line 70
Do NOT recreate tests that already exist — just note the match.
- Distinctive: line 151
If the scenario exercises additional features, add the relevant stages.
- Distinctive: line 201
Add a comment describing what the test covers:
- Distinctive: line 207
Do NOT reference the old JSON file name in comments — the files will be deleted.
- Very specific: line 211
Run the new tests with -update to create golden files:
- Distinctive: line 244
If types or fields that are central to what the test scenario is testing are missing or have a different structure, a pipeline stage may …
- Very specific: line 245
If types appear with the wrong representation (e.g., interface{} instead of a typed wrapper), look for a transformation stage that converts …
- Distinctive: line 270
All remaining golden file tests pass:
- Distinctive: line 275
All new pipeline tests pass:
- Very specific: line 315
MakeRequired()panics: if the property type is not already optional — always call MakeTypeOptional() first.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Skill: Migrate JSON Golden File Tests to Pipeline Unit Tests
This skill guides the migration of JSON-based golden file test scenarios (run by TestGolden in golden_files_test.go) into programmatic Go unit tests in the pipeline package. The new tests construct types using astmodel APIs and run specific pipeline stages, providing focused, maintainable unit tests.
Background
Two Test Systems
- JSON golden file tests (
v2/tools/generator/internal/codegen/golden_files_test.go):- JSON files in
testdata/<GroupName>/directories define schemas config.yamlspecifies options (hasArmResources,pipelines)- Runs through the FULL code generator pipeline (80+ stages)
- Tests are discovered automatically by walking the testdata directory
- Output includes webhooks, storage types, conversion functions, etc.
- JSON files in
- Pipeline unit tests (e.g.,
v2/tools/generator/internal/codegen/pipeline/create_arm_types_test.go):- Types are constructed programmatically using
astmodelAPIs andtesthelpers - Runs only the specific pipeline stages being tested
- Uses
test.AssertPackagesGenerateExpectedCode()for golden file comparison - Golden files stored in
pipeline/testdata/<TestFuncName>/directories
- Types are constructed programmatically using
The unit tests are preferred because they are faster, more focused, and don't depend on the JSON schema scanner.
Prerequisites
- You know the group name (the testdata subdirectory name, e.g.,
ArmResource) to migrate. - The testdata directory exists at
v2/tools/generator/internal/codegen/testdata/<GroupName>/.
Step-by-Step Procedure
Step 1: Examine the Existing JSON Scenarios
- List all
.jsonfiles inv2/tools/generator/internal/codegen/testdata/<GroupName>/. - Read
config.yamlin the same directory to understand test configuration:hasArmResources: true/false— determines which pipeline stages runpipelines:— which pipelines to test (azure, crossplane)
- Read each JSON file to understand what it tests:
- What resource definitions exist (
resourceDefinitionssection) - What types are defined (
definitionssection) - Property types (string, integer, boolean, array, map, enum, $ref, oneOf)
- Required vs optional properties (
requiredarrays) - Special features (resource references, nested resources, discriminated unions)
- What resource definitions exist (
Step 2: Determine Where to Place New Tests
Tests always go in a *_test.go file in the pipeline package (v2/tools/generator/internal/codegen/pipeline/), typically alongside the pipeline stage being tested.
To determine the specific file:
- Look at the
config.yamlto understand what features the scenarios exercise (e.g.,hasArmResources: truesuggests ARM type creation is involved). - Examine what the JSON scenarios test and find the corresponding pipeline stage.
- Place tests in the test file for that stage:
- ARM type creation and conversion →
create_arm_types_test.go - Enum handling → the enum-related test file
- Type alias removal → the type alias test file
- Other stage-specific scenarios → the corresponding
<stage_name>_test.gofile
- ARM type creation and conversion →
Step 3: Check for Existing Coverage
Before creating new tests, check the target test file for tests that already cover the same scenarios:
- Read the target test file completely.
- For each JSON scenario, determine if an existing test constructs equivalent types and runs the same pipeline stages.
- Note which scenarios already have coverage and which need new tests.
- Do NOT recreate tests that already exist — just note the match.
Step 4: Create the New Tests
For each JSON scenario that needs a new test, create a Go test function.
4a: Map JSON Schema to astmodel Types
| JSON Schema Pattern | astmodel Equivalent |
"type": "string" | astmodel.StringType |
"type": "integer" | astmodel.IntType |
"type": "boolean" | astmodel.BoolType |
"type": "object" with no properties | astmodel.NewMapType(astmodel.StringType, astmodel.AnyType) |
{} (empty schema) | astmodel.AnyType |
"type": "array", "items": X | astmodel.NewArrayType(X) |
"additionalProperties": X | astmodel.NewMapType(astmodel.StringType, X) |
"$ref": "#/definitions/Foo" | Reference the TypeName of the Foo definition |
"enum": [values] | astmodel.NewEnumType(baseType, astmodel.MakeEnumValue(id, `"value"`)...) — value must be backtick-quoted |
"oneOf": [refs] | Object with optional properties for each variant + astmodel.OneOfFlag.ApplyTo() |
4b: Handle Required/Optional Properties
The JSON schema scanner wraps ALL property types in optional and then annotates required ones:
// Required property:
prop := astmodel.NewPropertyDefinition("Name", "name", SomeType).MakeTypeOptional().MakeRequired()
// Optional property:
prop := astmodel.NewPropertyDefinition("Name", "name", SomeType).MakeTypeOptional()
// or equivalently:
prop := astmodel.NewPropertyDefinition("Name", "name", astmodel.NewOptionalType(SomeType))IMPORTANT: MakeRequired() panics if the property type is not already optional. Always call MakeTypeOptional() first.
4c: Create Resource Structures
For ARM resources (the common case):
spec := test.CreateSpec(test.Pkg2020, "ResourceName", properties...)
status := test.CreateStatus(test.Pkg2020, "ResourceName")
resource := test.CreateARMResource(test.Pkg2020, "ResourceName", spec, status, test.Pkg2020APIVersion)
defs := make(astmodel.TypeDefinitionSet)
defs.AddAll(resource, status, spec, /* other type defs... */ test.Pkg2020APIVersion)For resources with a nested properties object (common ARM pattern):
propsObj := test.CreateObjectDefinition(pkg, "ResourceNameProperties", prop1, prop2, ...)
propsProp := astmodel.NewPropertyDefinition("Properties", "properties", propsObj.Name()).MakeTypeOptional()
spec := test.CreateSpec(pkg, "ResourceName", test.NameProperty, propsProp)For resource ownership (parent-child relationships):
resourceBRT, _ := astmodel.AsResourceType(resourceB.Type())
resourceB = resourceB.WithType(resourceBRT.WithOwner(resourceA.Name()))4d: Choose Pipeline Stages
Determine which pipeline stages to run by examining what the existing tests in the target file use. Look at the other tests already in the file to understand the standard pattern for that stage, then replicate it for your new tests.
For example, create_arm_types_test.go uses this standard set:
state, err := RunTestPipeline(
NewState(defs),
CreateARMTypes(cfg, idFactory, logr.Discard()),
ApplyARMConversionInterface(idFactory, cfg),
SimplifyDefinitions(),
StripUnreferencedTypeDefinitions(),
)If the scenario exercises additional features, add the relevant stages. Look at existing tests in the same file for examples of how to include stages for specific features:
| Scenario | Additional Stages |
| Resource references | Configure OMC with ReferenceType.Set(config.ReferenceTypeARM) per property, then add ApplyCrossResourceReferencesFromConfig(configuration, logr.Discard()) and TransformCrossResourceReferences(configuration, idFactory) before the core stages. |
| Config maps | Add AddConfigMaps(configuration) before the core stages |
| Secrets | Add AddSecrets(configuration) before the core stages |
| Flattening | Add FlattenProperties(logr.Discard()) after the core stages |
| JSON/Any type fields | Add ReplaceAnyTypeWithJSON() before the core stages |
| OneOf | Standard stages work; the type must have OneOfFlag applied via ApplyObjectTransformation |
For resource references, use standard pipeline stages with OMC configuration — do NOT write custom test helper functions:
omc := config.NewObjectModelConfiguration()
g.Expect(
omc.ModifyProperty(
specProperties.Name(),
someProperty.PropertyName(),
func(pc *config.PropertyConfiguration) error {
pc.ReferenceType.Set(config.ReferenceTypeARM)
return nil
},
),
).To(Succeed())
configuration := config.NewConfiguration()
configuration.ObjectModelConfiguration = omc
state, err := RunTestPipeline(
NewState(defs),
ApplyCrossResourceReferencesFromConfig(configuration, logr.Discard()),
TransformCrossResourceReferences(configuration, idFactory),
CreateARMTypes(omc, idFactory, logr.Discard()),
ApplyARMConversionInterface(idFactory, omc),
SimplifyDefinitions(),
StripUnreferencedTypeDefinitions(),
)4e: Assert and Generate Golden Files
g.Expect(err).ToNot(HaveOccurred())
test.AssertPackagesGenerateExpectedCode(t, state.Definitions())4f: Test Naming and Comments
- Name tests descriptively:
TestCreateARMTypes_SimpleResourceMapProperties - Add a comment describing what the test covers:
// TestCreateARMTypes_SimpleResourceMapProperties tests that an ARM resource with various map property // types (maps of objects, maps of maps, maps of arrays, maps of enums, maps of strings) generates // correct ARM types and conversions. - Do NOT reference the old JSON file name in comments — the files will be deleted.
4g: Generate Golden Files
Run the new tests with -update to create golden files:
cd v2/tools/generator
go test ./internal/codegen/pipeline/ -run "TestName1|TestName2|..." -update -vThen verify they pass without -update:
go test ./internal/codegen/pipeline/ -run "TestName1|TestName2|..." -vAlso run all existing tests to confirm no regressions:
go test ./internal/codegen/pipeline/ -run "TestCreate" -vStep 5: Compare Old and New Golden Files
Generate a comparison report between the new pipeline test golden files and the old JSON test golden files. The expected differences fall into two categories:
Expected/benign differences (present in all scenarios due to running fewer pipeline stages):
- Package name: New uses
person(fromtest.Pkg2020), old usestest(from JSON schema URL) - API version string: New uses
"v2020", old uses the version from JSON schema (e.g.,"2020-01-01") - APIVersion/Type enum fields on ARM spec: Absent in new (not created by unit pipeline), present in old
- Status type: New has
Status stringfield, old may differ // Generated from:comments: Absent in new, present in old- Enum value formatting: New uses
Color_blue = Color(blue), old usesColor_Blue = Color("blue") - Additional interfaces/scaffolding: Old may include extra interfaces (Conditioner, Convertible, Exporter, etc.) that are generated by pipeline stages not included in the unit test
Potentially meaningful differences to investigate:
- If types or fields that are central to what the test scenario is testing are missing or have a different structure, a pipeline stage may need to be added to the unit test. Check what stage produces the expected output and add it.
- If types appear with the wrong representation (e.g.,
interface{}instead of a typed wrapper), look for a transformation stage that converts between representations and add it to the pipeline.
Run the comparison:
for scenario in "NewTestName:old_json_name"; do
new_name="${scenario%%:*}"
old_name="${scenario##*:}"
diff "pipeline/testdata/${new_name}/person-v20200101-arm.golden" \
"testdata/<GroupName>/${old_name}_azure_arm.golden"
donePresent the report to the user and get confirmation before proceeding.
Step 6: Delete the Old Tests
Once the user confirms the comparison looks good:
rm -rf v2/tools/generator/internal/codegen/testdata/<GroupName>/Then verify:
- All remaining golden file tests pass:
go test ./internal/codegen/ -run "TestGolden" -v -count=1 - All new pipeline tests pass:
go test ./internal/codegen/pipeline/ -v -count=1
Available Test Helpers
Package: test (v2/tools/generator/internal/test/)
test.Pkg2020,test.Pkg2021,test.Pkg2022— package referencestest.Pkg2020APIVersion— API version enum definitiontest.NameProperty,test.FullNameProperty,test.FamilyNameProperty,test.KnownAsProperty,test.RestrictedNameProperty— reusable propertiestest.CreateSpec(),test.CreateStatus(),test.CreateARMResource(),test.CreateResource()— resource builderstest.CreateObjectDefinition()— creates an ObjectType with propertiestest.AssertPackagesGenerateExpectedCode()— golden file assertiontest.CreateFolderForTest()— option for subtests needing unique golden file folders
Package: astmodel (v2/tools/generator/internal/astmodel/)
astmodel.NewPropertyDefinition(name, jsonName, type)— with.MakeTypeOptional(),.MakeRequired(),.WithDescription()astmodel.NewEnumType(baseType, values...),astmodel.MakeEnumValue(id, value)— enums. Thevalueparameter is a literal Go expression that appears in generated code; for string enums it must include quotes:astmodel.MakeEnumValue("blue", `"blue"`)astmodel.NewArrayType(element)— arraysastmodel.NewMapType(key, value)— mapsastmodel.NewOptionalType(element)— optional wrapperastmodel.OneOfFlag.ApplyTo(objectType)— oneOf flagastmodel.MakeTypeDefinition(name, type)— type definitionastmodel.MakeInternalTypeName(pkg, name)— type nameastmodel.AsResourceType(type)— cast to resource type (for ownership)resourceType.WithOwner(ownerName)— set resource ownership
Pipeline Stages (v2/tools/generator/internal/codegen/pipeline/)
RunTestPipeline(state, stages...)— run pipeline stages in sequenceNewState(defs)— create initial stateCreateARMTypes(),ApplyARMConversionInterface()— core ARM stagesSimplifyDefinitions(),StripUnreferencedTypeDefinitions()— cleanup stagesFlattenProperties()— flattening stageReplaceAnyTypeWithJSON()— convertsinterface{}tov1.JSONApplyCrossResourceReferencesFromConfig(),TransformCrossResourceReferences()— resource reference stagesAddConfigMaps(),AddSecrets()— configmap/secret stages
Common Pitfalls
MakeRequired()panics if the property type is not already optional — always callMakeTypeOptional()first.- Do NOT reference old JSON file names in test comments — the JSON files will be deleted.
- Do NOT write custom test helper functions for cross-resource references — use the standard
ApplyCrossResourceReferencesFromConfigandTransformCrossResourceReferencespipeline stages with OMC configuration. - Resource ownership is set via
ResourceType.WithOwner()which takesInternalTypeName, not a string pointer. - Golden file directories are named after the test function — test naming matters for file organization.
- The old and new tests exercise different pipeline depths — the new tests are more focused unit tests; differences in output scaffolding are expected and benign.
MakeEnumValuevalue parameter must be quoted for string enums — The second argument toMakeEnumValueis a literal Go expression emitted in generated code. For string enums, wrap the value in backtick quotes:astmodel.MakeEnumValue("blue", `"blue"`). Without quotes, the generated code will reference undefined identifiers (e.g.,Color(blue)instead ofColor("blue")).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

