Ctrl K
ComunidadGuías…

internal/handlers/manifest_params_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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")
}