diff --git a/internal/server/source_url_internal_test.go b/internal/server/source_url_internal_test.go index beaf1d5..d006c44 100644 --- a/internal/server/source_url_internal_test.go +++ b/internal/server/source_url_internal_test.go @@ -308,6 +308,70 @@ func TestGeneratedURLFetchesTheSourceURLAsEntered(t *testing.T) { } } +// TestGeneratorMakesOneImageURLForTwoSpellingsOfASourceURL enters two +// spellings of one source URL on the generator page, requests the encrypted +// URL made for each, and checks that the upstream receives one request and +// that the second encrypted URL is served from the cache. One pair writes the +// host in upper and in lower case, which /v1/image/ lower-cases. The host is +// an IPv6 address, whose hex digits have letter case, because the fetcher +// would look a host name up in DNS. It has a port, as the fetcher refuses an +// IPv6 address written without one. The other pair is a source URL with no +// path and with the path "/", both of which fetch "/". +func TestGeneratorMakesOneImageURLForTwoSpellingsOfASourceURL(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + // sourceURLs are the two spellings, after "https://" + sourceURLs []string + // wantRequest is the path the upstream must receive + wantRequest string + }{ + {"host in upper and in lower case", + []string{"[2001:DB8::A]:8443/a.png", "[2001:db8::a]:8443/a.png"}, + "/a.png"}, + {"no path and the path written as a slash", + []string{upstreamHost, upstreamHost + "/"}, "/"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + upstream := startRecordingUpstream(t) + s, _, _ := startImageProxy(t, upstream.Server) + + var imageURLs []string + + for _, sourceURL := range tt.sourceURLs { + rec := postGenerate(t, s, "https://"+sourceURL) + + match := generatedURLPattern.FindStringSubmatch(rec.Body.String()) + if rec.Code != http.StatusOK || match == nil { + t.Fatalf("POST /generate: %d with no encrypted URL; body %s", + rec.Code, rec.Body.String()) + } + + imageURLs = append(imageURLs, match[1]) + } + + getImage(t, s, imageURLs[0]) + + second := getImage(t, s, imageURLs[1]) + if got := second.Header().Get("X-Pixa-Cache"); got != string(imgcache.CacheHit) { + t.Errorf("second X-Pixa-Cache = %q, want HIT", got) + } + + got := upstream.requestTargets() + t.Logf("upstream received %q", got) + + if !slices.Equal(got, []string{tt.wantRequest}) { + t.Errorf("upstream received %q, want [%q]", got, tt.wantRequest) + } + }) + } +} + // TestGeneratorRefusesSourceURLItCannotFetchAsWritten submits source URLs the // generator page must make no encrypted URL for, and checks that it answers // 400 and shows why. pixa keeps the source path decoded and fetches it