internal/handlers/manifest_params_test.go
Rama main · réplica de lectura del repositorio original.
package handlers
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"xprem/internal/services"
"xprem/internal/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const paramsTestAppID = "d3a60200-4574-4781-9734-c174b511fb55"
func validManifestRequest(target string) *http.Request {
r := httptest.NewRequest(http.MethodGet, target, nil)
r.Header.Set("expo-app-id", paramsTestAppID)
r.Header.Set("expo-channel-name", "qa")
r.Header.Set("expo-protocol-version", "1")
r.Header.Set("expo-platform", "ios")
r.Header.Set("expo-runtime-version", "3.0.0")
return r
}
// Every header the manifest path consumes, asserted in one place. A field
// dropped from the params struct is invisible to the service tests, which build
// their params by hand.
func TestManifestParamsReadsEveryHeader(t *testing.T) {
r := validManifestRequest("/manifest")
r.Header.Set("xprem-branch", "pr-482")
r.Header.Set("xprem-surf-blocked", "pr-482@200")
r.Header.Set("EAS-Client-ID", "device-1")
r.Header.Set("expo-current-update-id", "11111111-1111-4111-8111-111111111111")
r.Header.Set("expo-embedded-update-id", "33333333-3333-4333-8333-333333333333")
r.Header.Set("expo-expect-signature", "true")
r.Header.Set("expo-fatal-error", "boom")
r.Header.Set("Expo-Recent-Failed-Update-Ids", `"22222222-2222-4222-8222-222222222222"`)
params, err := manifestParams(r, "req-1")
require.NoError(t, err)
assert.Equal(t, "req-1", params.RequestID)
assert.Equal(t, paramsTestAppID, params.AppID)
assert.Equal(t, "qa", params.ChannelName)
assert.Equal(t, types.PlatformIOS, params.Platform)
assert.Equal(t, "3.0.0", params.RuntimeVersion)
assert.Equal(t, int64(1), params.ProtocolVersion)
assert.Equal(t, "pr-482", params.XpremBranch)
assert.Equal(t, "pr-482@200", params.SurfBlockTokens)
assert.Equal(t, "device-1", params.ClientID)
assert.Equal(t, "11111111-1111-4111-8111-111111111111", params.CurrentUpdateID)
assert.Equal(t, "33333333-3333-4333-8333-333333333333", params.EmbeddedUpdateID)
assert.Equal(t, "true", params.ExpectSignature)
assert.Equal(t, "boom", params.ExpoFatalError)
assert.Equal(t, `"22222222-2222-4222-8222-222222222222"`, params.RecentFailedUpdateIDs)
}
// The two surf headers are the ones with no other test able to see them: the
// service tests set the fields directly, so only this one covers the wire.
func TestManifestParamsReadsTheSurfHeaders(t *testing.T) {
cases := map[string]struct {
header string
read func(p services.ManifestRequestParams) string
}{
"xprem-branch": {"xprem-branch", func(p services.ManifestRequestParams) string { return p.XpremBranch }},
"xprem-surf-blocked": {"xprem-surf-blocked", func(p services.ManifestRequestParams) string { return p.SurfBlockTokens }},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
r := validManifestRequest("/manifest")
r.Header.Set(tc.header, "sentinel")
params, err := manifestParams(r, "req-1")
require.NoError(t, err)
assert.Equal(t, "sentinel", tc.read(params))
})
}
}
// Absent optional headers are empty, never a stale value from another field.
func TestManifestParamsLeavesAbsentHeadersEmpty(t *testing.T) {
params, err := manifestParams(validManifestRequest("/manifest"), "req-1")
require.NoError(t, err)
assert.Empty(t, params.XpremBranch)
assert.Empty(t, params.SurfBlockTokens)
assert.Empty(t, params.ClientID)
assert.Empty(t, params.CurrentUpdateID)
assert.Empty(t, params.ExpoFatalError)
assert.Empty(t, params.RecentFailedUpdateIDs)
}
// Platform and runtime version fall back to the query string for clients that
// cannot set the headers.
func TestManifestParamsFallsBackToTheQueryString(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/manifest?platform=android&runtimeVersion=2.0.0", nil)
r.Header.Set("expo-app-id", paramsTestAppID)
r.Header.Set("expo-channel-name", "qa")
r.Header.Set("expo-protocol-version", "1")
params, err := manifestParams(r, "req-1")
require.NoError(t, err)
assert.Equal(t, types.PlatformAndroid, params.Platform)
assert.Equal(t, "2.0.0", params.RuntimeVersion)
}
func TestManifestParamsRejects(t *testing.T) {
// The legacy fallback would resolve an app id without the header.
t.Setenv("SKIP_LEGACY_APP_ID_FALLBACK", "true")
cases := map[string]func(*http.Request){
"no app id": func(r *http.Request) { r.Header.Del("expo-app-id") },
"no channel": func(r *http.Request) { r.Header.Del("expo-channel-name") },
"no protocol version": func(r *http.Request) { r.Header.Del("expo-protocol-version") },
"bad protocol version": func(r *http.Request) {
r.Header.Set("expo-protocol-version", "not-a-number")
},
"unknown platform": func(r *http.Request) { r.Header.Set("expo-platform", "blackberry") },
"no platform": func(r *http.Request) { r.Header.Del("expo-platform") },
"no runtime version": func(r *http.Request) { r.Header.Del("expo-runtime-version") },
}
for name, breakIt := range cases {
t.Run(name, func(t *testing.T) {
r := validManifestRequest("/manifest")
breakIt(r)
_, err := manifestParams(r, "req-1")
assert.Error(t, err)
})
}
}
// The device-facing half of the refusal: without this header the device keeps
// asking for the branch that crashed on it, on every poll, forever.
func TestServerDefinedHeadersCarriesTheVerdict(t *testing.T) {
// Tokens are opaque on the wire; the encoding is pinned in the services package.
params := services.ManifestRequestParams{SurfBlockTokens: "cHItMQAxMDA"}
result := services.UpdateDecision{BlockedSurf: &services.BlockedSurf{BranchName: "pr-2", Token: "cHItMgAyMDA"}}
w := httptest.NewRecorder()
writeServerDefinedHeaders(w, serverDefinedHeaders(params, result))
// The verdict the device already held survives the new one.
assert.Equal(t, `xprem-surf-blocked="cHItMgAyMDA,cHItMQAxMDA"`, w.Header().Get("expo-server-defined-headers"))
}
// No refusal means no header at all: an empty dictionary would replace whatever
// the device already carries.
func TestServerDefinedHeadersIsSilentWithoutARefusal(t *testing.T) {
params := services.ManifestRequestParams{SurfBlockTokens: "pr-1@100"}
w := httptest.NewRecorder()
writeServerDefinedHeaders(w, serverDefinedHeaders(params, services.UpdateDecision{}))
assert.Empty(t, w.Header().Get("expo-server-defined-headers"))
}
// The surf header is client-supplied and reaches the query layer and the logs.
// Its two siblings are both bounded before use; this one was not.
func TestRequestedBranchIsBounded(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/manifest", nil)
r.Header.Set("xprem-branch", strings.Repeat("b", maxRequestedBranchLen))
assert.Len(t, requestedBranch(r), maxRequestedBranchLen, "a name of the maximum length is still a name")
r.Header.Set("xprem-branch", strings.Repeat("b", maxRequestedBranchLen+1))
assert.Empty(t, requestedBranch(r), "one byte over must read as no surf, not as a branch")
}