From 14c75521570a3b309848489e576ba766b52d03e2 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 23 Feb 2026 23:57:07 +0100 Subject: [PATCH 1/5] test: add gateway-conformance test for DNSLink -> IPNS resolution existing DNSLink tests only cover dnslink=/ipfs/, but the dnslink=/ipns/ path is untested. reuse the Ed25519 IPNS record fixture from subdomain_gateway/ to verify the gateway resolves DNSLink -> IPNS -> content correctly. --- fixtures/dnslink_ipns/dnslink.yml | 6 +++++ tests/dnslink_gateway_ipns_test.go | 41 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 fixtures/dnslink_ipns/dnslink.yml create mode 100644 tests/dnslink_gateway_ipns_test.go diff --git a/fixtures/dnslink_ipns/dnslink.yml b/fixtures/dnslink_ipns/dnslink.yml new file mode 100644 index 000000000..1ca6092e2 --- /dev/null +++ b/fixtures/dnslink_ipns/dnslink.yml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../fixture.schema.json +dnslinks: + dnslink-over-ipns: + domain: dnslink-over-ipns.example.org + # Ed25519 IPNS key from subdomain_gateway fixtures (base58 peer ID) + path: /ipns/12D3KooWLQzUv2FHWGVPXTXSZpdHs7oHbXub2G5WC8Tx4NQhyd2d diff --git a/tests/dnslink_gateway_ipns_test.go b/tests/dnslink_gateway_ipns_test.go new file mode 100644 index 000000000..9bac1e615 --- /dev/null +++ b/tests/dnslink_gateway_ipns_test.go @@ -0,0 +1,41 @@ +package tests + +import ( + "testing" + + "github.com/ipfs/gateway-conformance/tooling" + "github.com/ipfs/gateway-conformance/tooling/car" + "github.com/ipfs/gateway-conformance/tooling/dnslink" + "github.com/ipfs/gateway-conformance/tooling/specs" + . "github.com/ipfs/gateway-conformance/tooling/test" +) + +func TestDNSLinkGatewayIPNS(t *testing.T) { + tooling.LogTestGroup(t, GroupDNSLink) + + dnsLinks := dnslink.MustOpenDNSLink("dnslink_ipns/dnslink.yml") + dnsLinkDomain := dnsLinks.MustGet("dnslink-over-ipns") + + // same content the Ed25519 IPNS record from subdomain_gateway points at + fixture := car.MustOpenUnixfsCar("subdomain_gateway/fixtures.car") + payload := string(fixture.MustGetRawData("hello-CIDv1")) + + tests := SugarTests{ + { + Name: "GET for DNSLink domain with dnslink=/ipns/{key} returns expected payload", + Hint: ` + When a DNSLink TXT record points to /ipns/ instead of /ipfs/, + the gateway must first resolve the IPNS name and then serve the content + it points to. + `, + Request: Request(). + Header("Host", dnsLinkDomain). + Path("/"), + Response: Expect(). + Status(200). + Body(payload), + }, + } + + RunWithSpecs(t, tests, specs.DNSLinkGateway) +} From f17e342b2cd8d9c89af056c47001bfe11e2ca553 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Tue, 24 Feb 2026 00:36:23 +0100 Subject: [PATCH 2/5] test: add subdomain gateway test for DNSLink -> IPNS resolution test both original and inlined DNSLink domain forms when the dnslink TXT record points at /ipns/ instead of /ipfs/. --- tests/subdomain_gateway_ipns_test.go | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/subdomain_gateway_ipns_test.go b/tests/subdomain_gateway_ipns_test.go index 997595a93..47db0ca0b 100644 --- a/tests/subdomain_gateway_ipns_test.go +++ b/tests/subdomain_gateway_ipns_test.go @@ -236,5 +236,35 @@ func TestSubdomainGatewayDNSLinkInlining(t *testing.T) { }, }...) + // DNSLink with dnslink=/ipns/{key} (resolves through IPNS) + dnsLinksIPNS := dnslink.MustOpenDNSLink("dnslink_ipns/dnslink.yml") + dnsLinkOverIPNS := dnsLinksIPNS.MustGet("dnslink-over-ipns") + + fixture := car.MustOpenUnixfsCar("subdomain_gateway/fixtures.car") + ipnsPayload := string(fixture.MustGetRawData("hello-CIDv1")) + + tests = append(tests, SugarTests{ + { + Name: "request for {dnslink}.ipns.{gateway} with dnslink=/ipns/{key} returns expected payload", + Hint: "DNSLink TXT record pointing at /ipns/ must be resolved recursively via IPNS", + Request: Request(). + Header("Host", Fmt("{{dnslink}}.ipns.{{host}}", dnsLinkOverIPNS, u.Host)). + Path("/"), + Response: Expect(). + Status(200). + Body(ipnsPayload), + }, + { + Name: "request for {inlined-dnslink}.ipns.{gateway} with dnslink=/ipns/{key} returns expected payload", + Hint: "DNSLink TXT record pointing at /ipns/ must be resolved recursively via IPNS (inlined form)", + Request: Request(). + Header("Host", Fmt("{{inlined}}.ipns.{{host}}", dnslink.InlineDNS(dnsLinkOverIPNS), u.Host)). + Path("/"), + Response: Expect(). + Status(200). + Body(ipnsPayload), + }, + }...) + RunWithSpecs(t, tests, specs.SubdomainGatewayIPNS) } From 609bead2ca6cf76abad97e47e80fff92468dc471 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Tue, 24 Feb 2026 01:00:29 +0100 Subject: [PATCH 3/5] chore: update changelog for v0.11.0 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f7998568..575d6f196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.11.0] - 2026-02-24 +### Added +- New test `TestDNSLinkGatewayIPNS` for DNSLink TXT records pointing at `/ipns/` instead of `/ipfs/`. All prior DNSLink tests only covered the `/ipfs/` case. [#272](https://github.com/ipfs/gateway-conformance/pull/272) +- New test cases in `TestSubdomainGatewayDNSLinkInlining` for the subdomain form of DNSLink -> IPNS resolution (`{dnslink}.ipns.{gateway}` and `{inlined-dnslink}.ipns.{gateway}`). [#272](https://github.com/ipfs/gateway-conformance/pull/272) + ## [0.10.1] - 2026-02-13 ### Fixed - Renamed `Base32Cid()` to `DNSSafeCidV1()` and fixed it to convert CIDv0 to CIDv1 before encoding. Uses base36 for `libp2p-key` codec (IPNS keys) and base32 for everything else, producing valid DNS-safe CID strings for subdomain gateway tests. [#264](https://github.com/ipfs/gateway-conformance/issues/264) From b93436ef7591c46b23e49d09000f3bbaf440f124 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Tue, 24 Feb 2026 01:22:04 +0100 Subject: [PATCH 4/5] test: add DNSLink -> IPNS fixture with CIDv1 libp2p-key representation test both base58btc peer ID and CIDv1-libp2p-key (base36) forms of IPNS names in DNSLink TXT records across gateway and subdomain tests. --- fixtures/dnslink_ipns/dnslink.yml | 6 +++- tests/dnslink_gateway_ipns_test.go | 25 ++++++++++--- tests/subdomain_gateway_ipns_test.go | 53 ++++++++++++++++------------ 3 files changed, 56 insertions(+), 28 deletions(-) diff --git a/fixtures/dnslink_ipns/dnslink.yml b/fixtures/dnslink_ipns/dnslink.yml index 1ca6092e2..a6b2596d6 100644 --- a/fixtures/dnslink_ipns/dnslink.yml +++ b/fixtures/dnslink_ipns/dnslink.yml @@ -2,5 +2,9 @@ dnslinks: dnslink-over-ipns: domain: dnslink-over-ipns.example.org - # Ed25519 IPNS key from subdomain_gateway fixtures (base58 peer ID) + # Ed25519 IPNS key from subdomain_gateway fixtures (base58btc peer ID) path: /ipns/12D3KooWLQzUv2FHWGVPXTXSZpdHs7oHbXub2G5WC8Tx4NQhyd2d + dnslink-over-ipns-cidv1: + domain: dnslink-over-ipns-cidv1.example.org + # same Ed25519 key as above, but using CIDv1 with libp2p-key multicodec (base36) + path: /ipns/k51qzi5uqu5dk3v4rmjber23h16xnr23bsggmqqil9z2gduiis5se8dht36dam diff --git a/tests/dnslink_gateway_ipns_test.go b/tests/dnslink_gateway_ipns_test.go index 9bac1e615..993575b85 100644 --- a/tests/dnslink_gateway_ipns_test.go +++ b/tests/dnslink_gateway_ipns_test.go @@ -14,7 +14,8 @@ func TestDNSLinkGatewayIPNS(t *testing.T) { tooling.LogTestGroup(t, GroupDNSLink) dnsLinks := dnslink.MustOpenDNSLink("dnslink_ipns/dnslink.yml") - dnsLinkDomain := dnsLinks.MustGet("dnslink-over-ipns") + dnsLinkB58 := dnsLinks.MustGet("dnslink-over-ipns") + dnsLinkCIDv1 := dnsLinks.MustGet("dnslink-over-ipns-cidv1") // same content the Ed25519 IPNS record from subdomain_gateway points at fixture := car.MustOpenUnixfsCar("subdomain_gateway/fixtures.car") @@ -22,14 +23,28 @@ func TestDNSLinkGatewayIPNS(t *testing.T) { tests := SugarTests{ { - Name: "GET for DNSLink domain with dnslink=/ipns/{key} returns expected payload", + Name: "GET for DNSLink with dnslink=/ipns/{peer-id} returns expected payload", Hint: ` - When a DNSLink TXT record points to /ipns/ instead of /ipfs/, - the gateway must first resolve the IPNS name and then serve the content + When a DNSLink TXT record points to /ipns/ (base58btc), + the gateway must resolve the IPNS name and then serve the content it points to. `, Request: Request(). - Header("Host", dnsLinkDomain). + Header("Host", dnsLinkB58). + Path("/"), + Response: Expect(). + Status(200). + Body(payload), + }, + { + Name: "GET for DNSLink with dnslink=/ipns/{cidv1-libp2p-key} returns expected payload", + Hint: ` + When a DNSLink TXT record points to /ipns/ (base36), + the gateway must resolve the IPNS name and then serve the content + it points to. + `, + Request: Request(). + Header("Host", dnsLinkCIDv1). Path("/"), Response: Expect(). Status(200). diff --git a/tests/subdomain_gateway_ipns_test.go b/tests/subdomain_gateway_ipns_test.go index 47db0ca0b..1f8b954ad 100644 --- a/tests/subdomain_gateway_ipns_test.go +++ b/tests/subdomain_gateway_ipns_test.go @@ -239,32 +239,41 @@ func TestSubdomainGatewayDNSLinkInlining(t *testing.T) { // DNSLink with dnslink=/ipns/{key} (resolves through IPNS) dnsLinksIPNS := dnslink.MustOpenDNSLink("dnslink_ipns/dnslink.yml") dnsLinkOverIPNS := dnsLinksIPNS.MustGet("dnslink-over-ipns") + dnsLinkOverIPNSCIDv1 := dnsLinksIPNS.MustGet("dnslink-over-ipns-cidv1") fixture := car.MustOpenUnixfsCar("subdomain_gateway/fixtures.car") ipnsPayload := string(fixture.MustGetRawData("hello-CIDv1")) - tests = append(tests, SugarTests{ - { - Name: "request for {dnslink}.ipns.{gateway} with dnslink=/ipns/{key} returns expected payload", - Hint: "DNSLink TXT record pointing at /ipns/ must be resolved recursively via IPNS", - Request: Request(). - Header("Host", Fmt("{{dnslink}}.ipns.{{host}}", dnsLinkOverIPNS, u.Host)). - Path("/"), - Response: Expect(). - Status(200). - Body(ipnsPayload), - }, - { - Name: "request for {inlined-dnslink}.ipns.{gateway} with dnslink=/ipns/{key} returns expected payload", - Hint: "DNSLink TXT record pointing at /ipns/ must be resolved recursively via IPNS (inlined form)", - Request: Request(). - Header("Host", Fmt("{{inlined}}.ipns.{{host}}", dnslink.InlineDNS(dnsLinkOverIPNS), u.Host)). - Path("/"), - Response: Expect(). - Status(200). - Body(ipnsPayload), - }, - }...) + for _, dnsLinkIPNS := range []struct { + name string + domain string + }{ + {"peer-id", dnsLinkOverIPNS}, + {"cidv1-libp2p-key", dnsLinkOverIPNSCIDv1}, + } { + tests = append(tests, SugarTests{ + { + Name: Fmt("request for {dnslink}.ipns.{gateway} with dnslink=/ipns/{{{name}}} returns expected payload", dnsLinkIPNS.name), + Hint: "DNSLink TXT record pointing at /ipns/ must be resolved recursively via IPNS", + Request: Request(). + Header("Host", Fmt("{{dnslink}}.ipns.{{host}}", dnsLinkIPNS.domain, u.Host)). + Path("/"), + Response: Expect(). + Status(200). + Body(ipnsPayload), + }, + { + Name: Fmt("request for {inlined-dnslink}.ipns.{gateway} with dnslink=/ipns/{{{name}}} returns expected payload", dnsLinkIPNS.name), + Hint: "DNSLink TXT record pointing at /ipns/ must be resolved recursively via IPNS (inlined form)", + Request: Request(). + Header("Host", Fmt("{{inlined}}.ipns.{{host}}", dnslink.InlineDNS(dnsLinkIPNS.domain), u.Host)). + Path("/"), + Response: Expect(). + Status(200). + Body(ipnsPayload), + }, + }...) + } RunWithSpecs(t, tests, specs.SubdomainGatewayIPNS) } From 85cec90a4c42c36bb628fd6c6e08b9eab267e892 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Tue, 24 Feb 2026 01:28:38 +0100 Subject: [PATCH 5/5] fix: use string concat instead of Fmt for test names with braces Fmt treats triple braces {{{...}}} as escaped literals, not template vars, causing "too many arguments" panic. --- tests/subdomain_gateway_ipns_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/subdomain_gateway_ipns_test.go b/tests/subdomain_gateway_ipns_test.go index 1f8b954ad..6826df794 100644 --- a/tests/subdomain_gateway_ipns_test.go +++ b/tests/subdomain_gateway_ipns_test.go @@ -253,7 +253,7 @@ func TestSubdomainGatewayDNSLinkInlining(t *testing.T) { } { tests = append(tests, SugarTests{ { - Name: Fmt("request for {dnslink}.ipns.{gateway} with dnslink=/ipns/{{{name}}} returns expected payload", dnsLinkIPNS.name), + Name: "request for {dnslink}.ipns.{gateway} with dnslink=/ipns/{" + dnsLinkIPNS.name + "} returns expected payload", Hint: "DNSLink TXT record pointing at /ipns/ must be resolved recursively via IPNS", Request: Request(). Header("Host", Fmt("{{dnslink}}.ipns.{{host}}", dnsLinkIPNS.domain, u.Host)). @@ -263,7 +263,7 @@ func TestSubdomainGatewayDNSLinkInlining(t *testing.T) { Body(ipnsPayload), }, { - Name: Fmt("request for {inlined-dnslink}.ipns.{gateway} with dnslink=/ipns/{{{name}}} returns expected payload", dnsLinkIPNS.name), + Name: "request for {inlined-dnslink}.ipns.{gateway} with dnslink=/ipns/{" + dnsLinkIPNS.name + "} returns expected payload", Hint: "DNSLink TXT record pointing at /ipns/ must be resolved recursively via IPNS (inlined form)", Request: Request(). Header("Host", Fmt("{{inlined}}.ipns.{{host}}", dnslink.InlineDNS(dnsLinkIPNS.domain), u.Host)).