TEST: Add golden-file tests for provider record conversion - #4653
TEST: Add golden-file tests for provider record conversion#4653shuvamk wants to merge 15 commits into
Conversation
Implements the harness requested in DNSControl#4622: record a provider's native API data once, replay it through the provider's conversion functions in "go test", and compare the result with a checked-in golden file. No credentials, no network. Following the decisions in DNSControl#4622 (comment): 1. Based on release_candidate_v5. 2. A golden line is LineString() plus the metadata, with the TTL and the space after it omitted when the TTL is zero. The format is built in the harness rather than by calling models.LineString(), so that LineString() keeps its current output. 3. -update rewrites only the golden files. The recorded inputs are never rewritten, so refreshing a golden never reaches for an API token. Gathering inputs from a live account stays a separate step. 5. Runs in "go test". 6. One data file per provider per function tested, named after the provider in lower case. Providers are enrolled one at a time by adding a short adapter that maps the provider's local signature onto a uniform one. A provider with no recorded data is skipped rather than failed, so the providers that are not enrolled yet stay green. Enrolled here: websupport (toRecordConfig, toNative), packetframe (toRc, toReq) and porkbun (toRc, toReq). websupport's goldens are checked against the expectations in its existing hand-written convert_test.go; packetframe and porkbun had no test file of any kind. The recorded inputs are written from each provider's own native struct and the shapes its converter parses, not captured from a live account, so a provider author with credentials should expect to swap them out. Point 4 (autogold) is read as applying to the pkg/js/parse_tests conversion, which is a separate PR, so -update is implemented here directly and adds no dependency. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Changes to the requirements
Responses to your earlier questions:
|
formatRecord() dropped the TTL and its trailing space when the TTL was zero, so a zero-TTL record rendered as "fwd IN URL ..." instead of "fwd 0 IN URL ...". parseRecords() then had to guess whether the second field of a line was a TTL or the class in order to read that back. The TTL is now written unconditionally. parseRecord() splits a line into its five fixed fields in one step and checks the count once, instead of cutting the line field by field and testing each cut. The conditional that guessed whether the second field was a TTL is gone, and the whole function goes from 40 lines to 32. Two side effects of that rewrite, both in error reporting. A TTL that does not parse is now named as such: "www abc IN A 192.0.2.1" reported `expected class "IN"` and now reports `strconv.ParseUint: parsing "abc": invalid syntax`. And parseRecord() no longer shadows its "line" parameter with the metadata-stripped record, so its error messages quote the whole line rather than truncating it at the semicolon. All six .golden files were regenerated with "go test <pkg> -update" and are byte-identical: none of the recorded records has a zero TTL. The only recorded input that encoded the old format is porkbun's .records, where the URL and URL301 records now carry an explicit 0. Requested by @TomOnTime in DNSControl#4653 (comment) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Ask 1 is done. Asks 2, 3, 4 and 5 are not in this push — detail at the bottom, but flagging it up front since you asked for all of them in this PR.
Two side effects of that rewrite, both in error reporting, both improvements but worth naming since they were not part of the ask:
Goldens: I ran Tests: Local run on On On the rest of your list: asks 2, 3, 4 and 5 — recording from |
|
Yes, continue. The TTL change is approved. |
The golden-file harness needs a function it can call with a native record and a DomainConfig. These providers had no such function: the conversion ran inline in GetZoneRecords, or inline in the method that makes the API call, so there was nothing to replay recorded data through. Extracted, one function per provider, no behaviour change: akamaiedgedns nativeToRecords(dc, akarecset) getRecords hetznerv2 nativeToRecords(dc, rrSet, zoneTTL) GetZoneRecords inwx toRecordConfig(dc, record) GetZoneRecords netlify toRecordConfig(dc, r) GetZoneRecords oracle toRecordConfig(dc, record) GetZoneRecords rwth toRecordConfig(dc, apiRecord) getAllRecords No signature takes an origin or a domain: every one of these functions used dc.Name only for the API call, which stays in the caller. hetznerv2 is the only one with a third parameter, the zone's default TTL, which the RRSet does not carry and cannot be derived from dc. Records that are dropped rather than converted (SOA everywhere, NETLIFY and NETLIFYv6 in netlify, the locked records in rwth) are now dropped by returning a nil RecordConfig, and the callers skip nils. That follows what dynu, exoscale and ovh already do, and it keeps the decision inside the function, so replaying a recorded zone that contains an SOA produces the same records the provider produces today. The counts in the survey posted to DNSControl#4622 were measured with a looser rule ("no free function returning *models.RecordConfig") and were also one too high: vercel was listed as inlined but already had vercelRecordToRC. Of the 15 providers that rule flags today, 8 do have a separable converter, it just returns models.Records or hangs off a type. 7 genuinely had none; axfrddns is the seventh and is left alone because DNSControl#4358 rewrites that function onto dnsv2. Behaviour preservation was checked per provider with a throwaway A/B test that ran the pre-extraction loop, copied verbatim from 1c629cf, and the extracted function over the same inputs in the same binary, comparing label, FQDN, TTL, type, RDATA, metadata and Original. All six agree byte for byte across every branch, including the error paths. Deleting rc.Original in oracle, or the NETLIFY skip in netlify, turns the corresponding comparison red. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Ask 5 is done, for 6 of the 7 providers that need it. Asks 2, 3 and 4 are not in this push. The "16" was our number, and it was wrongThat count came from the survey I posted to #4622, so this is me correcting myself, not you. It was measured with a loose rule — "provider declares no free function returning Re-run on
Extracted (6)
No signature takes an origin or a domain. All six had
One judgement call, easy to reverse if you'd ratherRecords that are dropped rather than converted — SOA everywhere, It matters for the golden replay: with the filter left caller-side, replaying a recorded zone containing an SOA emits a golden line the provider never actually produces. I checked that rather than assuming — a throwaway netlify fixture with an SOA and a VerificationNo behaviour change, checked per provider rather than reasoned about: a throwaway A/B test ran the pre-extraction loop, copied verbatim from
Not in this push
One thing worth flagging for whoever writes the netlify golden: |
The golden harness needs recorded data, and that data has to come from a
real provider. "Without executing a shell command" is read here as: our
code must not exec the Go toolchain and scrape its output. The command
stays the one already documented; the recording happens inside the
process it starts.
providergolden.Recorder collects both kinds of harness input:
- the records a provider is asked to store, written as <name>.records,
which is what CheckToNative replays;
- the native record each returned record came from, read from
RecordConfig.Original and written as <name>.json, which is what
CheckToRC replays.
providergolden.Record wraps a models.DNSProvider and observes them at
GetZoneRecordsCorrections. That is the one point where both are in the
form the provider's own conversion functions see them: dc.Records has
been downcased, canonicalized and punycoded by zonerecs, and existing
still carries each native record in Original. All four integration tests
are wrapped; the three that call zonerecs.CorrectZoneRecords contribute
data. TestNameserverDots only calls GetNameservers, so it contributes
none. No provider code changes.
integrationTest gains "-record <dir>". Without it nothing is collected,
no file is written and the provider is not wrapped, so a normal run is
unchanged. With it:
go test -run TestDNSProviders -timeout 1h -failfast -v ./integrationTest \
-args -verbose -profile CLOUDFLAREAPI -record providers/cloudflare/testdata
Duplicates are discarded and the output is sorted, so a run that revisits
the same records over hundreds of test cases produces a small file that
is the same on every run. Recording BIND this way yields 339 unique
records over 23 types; all 339 lines parse back through the harness's own
parser and re-render byte for byte, and two runs produce identical files.
<name>.json is written only for providers that fill in Original. Of the
21 providers named on the PR, 17 assign it at a9374d8; bind, axfrddns,
mythicbeasts and transip do not, so those record only the .records half.
Original is read where the wrapper sees it, which is not always what the
API sent. providers/netlify canonicalizes a CNAME, MX or NS value in
place before assigning the record to Original, so a native recorded from
it carries a trailing dot the API did not send and a golden replayed from
it never exercises that canonicalization. No wrapper can see the earlier
value: the mutation happens inside the provider's own converter. The
documentation says so and says to check recorded natives against the
API's responses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Ask 2 is done. Asks 3 and 4 are not, and I don't think I can do them — see the bottom. First, my reading of the ask, since it is a reading: "without executing a shell command" I took to mean our code must not exec the Go toolchain and scrape its output. So the command stays the one you already run — you just append go test -run TestDNSProviders -timeout 1h -failfast -v ./integrationTest \
-args -verbose -profile CLOUDFLAREAPI -record providers/cloudflare/testdataThe recording happens inside the process that command starts. If you meant something else — a The hook
func (p *recordingProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, existing models.Records) ([]*models.Correction, int, error) {
p.rec.Observe(dc.Records, existing)
return p.DNSProvider.GetZoneRecordsCorrections(dc, existing)
}That is the one place where both harness inputs exist in the form the provider's own converters see them. All four tests in Two files come out, named after the profile:
Rename them to match whatever you call the test. Duplicates are dropped and the output is sorted, so a run that hits the same records across hundreds of test cases gives a small file that is identical every time. One thing you should know before enrolling anything from a recording
Same for MX and NS. A, AAAA, TXT and CAA are untouched. Nothing is corrupted, but the fixture has the canonicalization pre-applied, so a golden replayed from it never exercises the line it exists to pin. The mutation happens inside the provider's own converter, before Netlify's three-field SRV branch at I have not swept the other 16 What I could actually runBIND is the only provider I can drive, so that is my only live evidence, and it covers the 339 unique records over 23 types (A AAAA CAA CNAME DHCID DNAME DNSKEY DS HTTPS LOC MX NAPTR NS OPENPGPKEY PTR RP SMIMEA SOA SRV SSHFP SVCB TLSA TXT). Two runs, byte-identical. Fed back through the harness's own BIND produces no TestsSix tests in
That last test also pins that the wrapper returns the provider's own corrections, count and error unchanged. The mutation pass earned its keep twice: my first draft had a redundant early return in One side effect worth naming: Local runOn Asks 3 and 4 — I can build the mechanism, but I cannot produce the dataBoth need credentials for accounts I do not have and will not ask you for. What I can honestly deliver is what is in this push: the mechanism, so you can produce the data yourself with one flag on the command you already run. I am not going to hand-write fixtures to make the list look finished. A wrong golden is permanent, and it is the same failure mode as netcup — the file would look authoritative while certifying whatever I guessed. If you run
|
Resolves the conflict DNSControl#4671 created with the ask-5 extraction. DNSControl#4671 ("NETLIFY: toRC should not mutate native record") was written against the inline conversion loop in GetZoneRecords. 22cc55d had already moved that loop into toRecordConfig, so the two edits landed on the same lines. The resolution carries DNSControl#4671's semantics into toRecordConfig verbatim: the dnsutil.Canonical in-place mutation of r.Value for CNAME/MX/NS and the three-field-SRV `r.Value += "."` hack are gone, and nrc.Flags{TargetIsFqdnNoDot: true} is passed on the MX, SRV, CAA and default arms. The strings and dnsutil imports go, pkg/nrc arrives. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…arness
Each file adapts one provider's record conversion functions to CheckToRC /
CheckToNative and names the recorded data after the provider and the function
under test. All 22 tests skip today ("<name> has no recorded data yet"), so
dropping a -record capture into providers/<x>/testdata/ and running
"go test ./providers/<x>/ -update" produces the goldens with no further code.
The adapters are per-provider because the signatures are: cloudflare's
nativeToRecord is a method; ns1's convert, route53's and azure's
nativeToRecords and gandiv5's nativeToRecords already return several records;
gcloud's nativeToRecord takes one rdata string out of a set, so the adapter
loops over set.Rrdatas the way getZoneSets does; digitalocean's and netlify's
toReq return no error; cnr's createRecordString is a method that also needs the
domain; vercel's toVercelCreateRequest needs the domain; luadns's
recordsToNative takes a slice but maps each record independently, so a
one-element slice exercises it faithfully.
Providers of the 21 that are not enrolled here, and why:
BIND ParseZoneContents takes a whole zone file, not a record
MYTHICBEASTS zoneFileToRecords takes an io.Reader
AXFRDDNS no separable converter (DNSControl#4658 rewrites it)
POWERDNS toRecordConfig needs the RRset's name/TTL/type, but
RecordConfig.Original holds only zones.Record{Content,Disabled};
buildRecordList is the only encoder and it takes a diff2.Change
NETNOD same on both counts: Original is
netnodPrimaryDNS.Record{Content,Disabled}
CNR and TRANSIP get the toNative half only: CNR sets Original to
deleteRecordString(rc), a string rather than the map[string]string toRC reads,
and TRANSIP never sets Original at all, so -record cannot produce a .json for
either.
The set-level encoders are left out because CheckToNative converts one record at
a time: gandiv5's recordsToNative, azuredns's and azureprivatedns's
recordToNativeDiff2, gcloud's mkRRSs and ns1's buildRecord. Each merges a whole
recordset into one native, so calling it with a one-element slice would not
exercise what it is for.
testDomain is "example.com" in every file; it has to match the zone the data
was recorded against.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rded zone Nothing enforces that the domain passed to CheckToRC / CheckToNative is the zone the fixture was recorded against, and getting it wrong does not fail. Replaying a fixture recorded from realzone.net through a test that says example.com leaves every label fully qualified in the golden: www.realzone.net 300 IN A 192.0.2.1 LabelFromFQDNNoDot prints "ERROR: ... called WRONG" but returns the name lowercased rather than shortened, so the test passes, -update writes that golden and it becomes the baseline. Executed against providers/netlify. Also drops netlify as the named example from the Original warning: DNSControl#4671 removed its in-place canonicalization of CNAME, MX and NS values, so the example no longer holds. The hazard it illustrates is general, so only the example goes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Asks 3 and 4, as far as they go without your credentials. Read this first: the test's domain has to match the zone you record against, and getting it wrong does not fail This is the one thing that will bite you across 21
If you would rather the harness refuse instead of warning — #4671 carried into Clearing the conflict, netlify's import block auto-merged cleanly while To check I had not changed behaviour, I ran your One perturbation per behaviour, to check the corpus actually discriminates:
The CAA gap is not a corpus defect. My earlier netlify objection is withdrawn I said netlify's converter mutated its argument and stored the canonicalised value in That was only ever a netlify finding. The other 16 of your 21 profiles that assign Ask 4: 16 of the 21 enrolled, 5 that cannot be One
Not enrolled, with the reason:
Two providers get only the If you want POWERDNS and NETNOD in, pointing The set-level encoders are deliberately left out, because Ask 3 needs your credentials. What to run. Per profile: go test -run TestDNSProviders -timeout 1h -failfast -v ./integrationTest \
-args -verbose -profile CLOUDFLAREAPI -record providers/cloudflare/testdataThat writes go test ./providers/cloudflare/ -updateBesides the domain constant above, one provider-specific thing to know: CLOUDFLAREAPI writes three different shapes into CLOUDFLAREAPI is the only one that needs this. Of the other 15, thirteen write a single native type into Local gate at
To confirm the enrolments are wired and not merely compiling, I hand-wrote a three-record fixture for DIGITALOCEAN (A, MX, CAA) and ran and |
Resolves the conflict DNSControl#4681 created with the ask-5 extraction. DNSControl#4681 ("use dc.LabelFromShort() even when not required for safety") changed `dc.NewRecordConfigParse(rrSet.Name, ...)` to `dc.NewRecordConfigParse(dc.LabelFromShort(rrSet.Name), ...)` in hetznerv2's inline conversion loop. 22cc55d had already moved that loop into nativeToRecords, so the two edits landed on the same lines. The resolution carries DNSControl#4681's semantics into nativeToRecords verbatim and keeps the extraction; the net change against release_candidate_v5 is the extraction alone. It is the only conflict in the 22 commits from b9823fb to e5dc8f6 (v5.0.0-rc2/3/4); no other file this branch touches was modified upstream. `go test ./providers/{packetframe,porkbun,websupport}/ -update` rewrites the recorded goldens byte-identically on top of rc4, so the dnsv1-to-dnsv2 upgrade (DNSControl#4683) and the RC.DnsKey*/.Loc*/.Smimea* removal (DNSControl#4686) did not move any recorded conversion output. No golden is regenerated here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
I've tested this and here is my feedback:
|
…ronment Addresses three of the four points raised in review. The documented -record path was relative to integrationTest/ rather than to the repo root, so the command in provider-conversion-tests.md wrote to integrationTest/providers/<name>/testdata. Executed with -profile BIND: the documented shape landed bind.records under integrationTest/. -record is now a bool, and the destination defaults to the testdata directory of the package the provider under test is implemented in, derived from the provider's own type: -profile BIND writes providers/bind/testdata/bind.records. -recorddir overrides it and implies -record. A relative -recorddir is still relative to integrationTest/ and needs the ../ prefix, which the documentation now states and shows. Passing a directory to -record, the old spelling, now fails naming the new one instead of being silently dropped by flag parsing. testDomain comes from providergolden.Domain(<PROVIDER>), which reads $<PROVIDER>_DOMAIN and falls back to "example.com" - the same variable integrationTest takes its test zone from. Applied to the 16 providers whose data is not recorded yet. packetframe, porkbun and websupport keep the literal, because their committed goldens are hand-written against example.com and would no longer match for anyone who has those variables set. One consequence of that, executed against a throwaway netlify fixture: a golden recorded while <PROVIDER>_DOMAIN names a real zone only matches while that variable still names it, so it does not match in a checkout that leaves it unset. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous commit claimed that passing a directory to -record "now fails
naming the new one instead of being silently dropped by flag parsing". That
was wrong, and this corrects it. -record is a bool, so the directory is a
positional argument, and Go's flag package stops parsing there:
-args -verbose -record ../providers/bind/testdata -profile BIND
lost -profile, took getProvider's "No -provider or -profile specified" path
and exited 0 having recorded nothing. The guard meant to catch it sat below
that early return, so it never ran. It is now the first thing getProvider
does, and no longer conditional on -record: integrationTest takes no
positional arguments, so a stray one is always a mistake. The same command
now reports
unexpected argument "../providers/bind/testdata"; the recording directory
is set with -recorddir
A relative -recorddir was interpreted by the test binary's own working
directory, so -recorddir providers/bind/testdata wrote to
integrationTest/providers/bind/testdata - the same surprise under a new flag
name, which the previous commit documented rather than fixed. It is now
resolved against the module root, the root TestdataDir already derives, so
the ../ prefix is no longer needed and the note about it is gone.
The package doc and the developer documentation offered
providergolden.Domain("WEBSUPPORT") as the example to copy. websupport is one
of the three providers deliberately left on the literal, and its
convert_test.go already declares const testDomain, so a provider author
following the example there gets a duplicate declaration. The example is
netlify now.
Executed: the misordered command above (fails, naming the argument),
-profile BIND -recorddir providers/bind/testdata (writes to
<repo>/providers/bind/testdata), bare -record (writes to the provider's own
testdata directory), and BIND_DOMAIN still selects the zone under test.
go build, go test -count=1 ./... (76 ok, 0 FAIL), golangci-lint, staticcheck,
go vet and the six CI go-checks commands are clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `CheckToNative` example in provider-conversion-tests.md did not
compile. It passed netlify's `toReq` directly, but that function is
`func(rc *models.RecordConfig) *dnsRecordCreate` and `CheckToNative[N]`
takes `func(rc *models.RecordConfig) (N, error)`:
vet: in call to providergolden.CheckToNative, type
func(rc *models.RecordConfig) *dnsRecordCreate of toReq does not
match func(rc *models.RecordConfig) (N, error) (cannot infer N)
The section is headed "That adapter is the only code you write", and
providers/netlify/convert_golden_test.go wraps `toReq` in a closure for
exactly this reason, so the page contradicted the file it names. The
snippet is now byte-identical to that file, and both Go blocks of the
section compile as written.
`recordingDir` gained a test. The previous commit changed it to resolve
`-recorddir` through `providergolden.ResolveDir`, but only `ResolveDir`
itself was covered: reverting the call site left `go test ./...` green.
With the call site reverted the new test reports
recordingDir() = "providers/bind/testdata", want an absolute path
Also drops the editorialising clause from the `ResolveDir` comment.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
There seems to be a bug or maybe I don't understand how to use this. When I run the The log shows... When I run the tests, TestToRcGolden reports "cloudns_torc has no recorded data yet" and TestToReqGolden reports "cloudns_toreq has no recorded data yet" Here is the full output: Please fix this and verify that any other provider that was converted has the same fix. |
`-record` wrote `<profile>.records` and `<profile>.json`, but CheckToRC and
CheckToNative read `<name>.json` and `<name>.records` for the name the test
passes, and every enrolled provider named that after the function it wraps.
Nothing lined up, so a fresh recording was invisible: a CLOUDNS run wrote
providers/cloudns/testdata/cloudns.records and cloudns.json, and
providers/cloudns still reported
--- SKIP: TestToRcGolden: cloudns_torc has no recorded data yet
--- SKIP: TestToReqGolden: cloudns_toreq has no recorded data yet
The recorder cannot name a file after a conversion function: it observes a
provider, not a function, and it produces exactly one `.records` and one `.json`
per run. vercel is where that bites, with two CheckToNative functions and one
recording. So the two names are separated instead of merged. A recorded input
belongs to the provider and is named after it; the golden belongs to the test
and keeps the name the test passes. vercel's two functions now replay the same
`vercel.records` into two goldens, which is what covering both of them means.
The recording is named after the package the provider is implemented in, the
same reflection that already picks the testdata directory, rather than after
`-profile`. A profile name is chosen by whoever wrote profiles.json, and for
four providers the type does not match the directory either: CLOUDFLAREAPI
lives in providers/cloudflare, GANDI_V5 in providers/gandiv5, and both Azure
types in providers/azuredns and providers/azureprivatedns. The tests take the
same name from the directory they run in, so the two ends agree by construction
and a recording needs no renaming.
The six committed inputs are renamed; their goldens and contents are unchanged.
All 19 enrolled providers now resolve to `testdata/<package>.{json,records}`.
Verified end to end without credentials by recording a BIND run and replaying
it through a throwaway CheckToNative: before, the test skipped with "no
recorded data yet" while the 341-record recording sat in the directory it
reads; after, it read all 341.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
I'm still getting the same error: Then I ran Now the error message is gone: However now I'm confused. Does the golden file record everything and That seems like an extra step. Please post a comment explaining if I'm using these tools as expected and make recommendations for how I should use them better or how we can improve the code. |
This is the harness from #4622, built to the decisions in #4622 (comment).
It is the harness plus three providers. Enrolling the rest is mechanical and I am happy to do it in follow-up PRs — see the last section.
What it does
Record a provider's native API data once.
go testreplays it through that provider's conversion functions and compares the result with a checked-in golden file. No credentials, no network.The whole of what a provider author writes is an adapter that maps their local signature onto a uniform one:
That closure is what absorbs the differing function names, argument orders, and one-vs-many returns.
websupport.toNativeandporkbun.toReqalready match the target signature, so their tests are one line.The decisions, and where each one landed
release_candidate_v5LineString()+ metadata, no TTL and no space after it when the TTL is 0formatRecord,pkg/providergolden/providergolden.go— see the note below-updateis (a) only, (b) is separate-updaterewrites.goldenonly; it never rewrites a recorded input and never opens a socketpkg/js/parse_testsseparately-updateis implemented here directly, no new dependency. If you would rather this used autogold, say so and I will switch itgo testgo test ./...websupport_torecordconfig,porkbun_toreq, …On decision 2, one choice you should overrule if you disagree.
formatRecordreimplements the line rather than callingmodels.LineString(), because the requested format differs from whatLineString()returns today (no TTL when zero, plus metadata) and changingLineString()would change behaviour outside the tests —providers/tencentdns/convert_test.goasserts against it. The cost is that there are now two definitions of the line format that can drift.LineString()'s own doc comment says "This may change some day to include metadata and other fields, skip zero TTLs, and more", so if you meant forLineString()itself to move, say so: the harness then becomes a one-line call and the drift risk goes away. I did not want to make that call for you inside a test PR.And:
toRC()and equivalents —CheckToRC.toNative/createRequestequivalents where it is easy —CheckToNative, wired up for all three providers.Metadata is appended after a
;, sorted, values quoted, so a line stays greppable and a diff stays one line per record. porkbun's URL forwarding records also carry no TTL, so they show both rules at once:The zero-TTL rule is pinned directly by
TestFormatRecord/zero_TTL_is_omittedin the harness's own tests.The
.goldenaCheckToRCtest produces is a valid input file for aCheckToNativetest, which is how thetoNativeside gets its input without a second recording format.Providers enrolled
websupport— chosen because it already has a hand-writtenconvert_test.go(yours after #4584, the author's before that), so its goldens can be checked against expectations somebody wrote independently of this harness. All six of its cases agree exactly:convert_test.goexpectswebsupport_tonative.golden@→1.2.3.4{"type":"A","name":"@","content":"1.2.3.4","ttl":3600}www→ghs.example.net(dot stripped){"type":"CNAME","name":"www","content":"ghs.example.net",…}@→mail.example.com{"type":"MX","name":"@","content":"mail.example.com","priority":10,…}_sip._tcp→sip.example.com{"type":"SRV","name":"_sip._tcp","content":"sip.example.com","priority":10,"port":5060,"weight":20,…}ipv6→2a00:4b40:aaaa:2001::6{"type":"AAAA","name":"ipv6","content":"2a00:4b40:aaaa:2001::6",…}@→hello world{"type":"TXT","name":"@","content":"hello world",…}and
websupport_torecordconfig.goldenreproduces the RDATA that test'smkRC()builds, record for record.TestRoundTripstill passes unchanged alongside the new tests.packetframeandporkbun— neither had a test file of any kind, and neither is in the integration matrix. Between the three there are both directions, three naming conventions, an extra parameter (packetframe.toReq(zoneID, rc)), metadata (porkbun's URL forwarding), and SVCB/HTTPS, whichb3d616fe(PORKBUN: Modernize field access for SVCB records, #4649) rewrote two commits before the rc3 tag.Where the recorded data came from, stated plainly: I do not have accounts with these providers. The inputs are written from each provider's own native struct and the content shapes its converter parses, cross-checked against
websupport's existing test where one exists. They are realistic, not authentic. A provider author with credentials should replace the.jsonand.recordsfiles with a real recording — that is a manual file swap, after which-updateregenerates the goldens from the new inputs.Verification
Everything below was executed on
779898cc. Test output is verbatim; the only edit is thatgithub.com/DNSControl/dnscontrol/v5/providers/...is shortened to.../providers/...in the SKIP and-updatetranscripts.A wrong value in a golden fails. Three independent corruptions, each reverted afterwards:
websupporttoRC golden, one label character:www->wwwwwebsupporttoNative golden, a trailing dot the API must not be sent:porkbuntoRC golden, one SVCB parameter:port="8443"->port="9443"A provider with no recorded data passes. Both halves, plus a provider that was never enrolled:
-updatereproduces the committed goldens byte for byte:Full local gate, base
release_candidate_v5@1c629cfc(v5.0.0-rc3):go test -count=1 ./...golangci-lint run ./...staticcheck ./...go vet ./...go build+bin/fmtjson+go mod tidy+go generate ./...+go fmt ./...+go fix ./..., thengit statusgo.mod/go.sumunchanged, no new dependency)BIND_DOMAIN=example.com go test ./integrationTest/ -args -provider BINDThe packages that change state are
packetframeandporkbun(no test files → ok) andpkg/providergoldenitself, which has its own tests for the line format, the parser, the skip-when-missing behaviour and the diff.websupportwas alreadyok.Deliberately not in this PR
GetZoneRecords(hetznerv2,vercel, …). Those need the function extracted first, which is a change to provider code and not a test change.pkg/js/parse_tests/ autogold, per decision 4.netcup. I had it enrolled and took it back out: its currentfromRecordConfigoutput would have codified a behaviour change, and a test-harness PR is the wrong place either to bless that or to fix it. Filed separately as an issue.