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) diff --git a/fixtures/dnslink_ipns/dnslink.yml b/fixtures/dnslink_ipns/dnslink.yml new file mode 100644 index 000000000..a6b2596d6 --- /dev/null +++ b/fixtures/dnslink_ipns/dnslink.yml @@ -0,0 +1,10 @@ +# yaml-language-server: $schema=../fixture.schema.json +dnslinks: + dnslink-over-ipns: + domain: dnslink-over-ipns.example.org + # 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 new file mode 100644 index 000000000..993575b85 --- /dev/null +++ b/tests/dnslink_gateway_ipns_test.go @@ -0,0 +1,56 @@ +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") + 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") + payload := string(fixture.MustGetRawData("hello-CIDv1")) + + tests := SugarTests{ + { + Name: "GET for DNSLink with dnslink=/ipns/{peer-id} returns expected payload", + Hint: ` + 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", 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). + Body(payload), + }, + } + + RunWithSpecs(t, tests, specs.DNSLinkGateway) +} diff --git a/tests/subdomain_gateway_ipns_test.go b/tests/subdomain_gateway_ipns_test.go index 997595a93..6826df794 100644 --- a/tests/subdomain_gateway_ipns_test.go +++ b/tests/subdomain_gateway_ipns_test.go @@ -236,5 +236,44 @@ 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")) + + for _, dnsLinkIPNS := range []struct { + name string + domain string + }{ + {"peer-id", dnsLinkOverIPNS}, + {"cidv1-libp2p-key", dnsLinkOverIPNSCIDv1}, + } { + tests = append(tests, SugarTests{ + { + 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)). + Path("/"), + Response: Expect(). + Status(200). + Body(ipnsPayload), + }, + { + 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)). + Path("/"), + Response: Expect(). + Status(200). + Body(ipnsPayload), + }, + }...) + } + RunWithSpecs(t, tests, specs.SubdomainGatewayIPNS) }