Files
nezha_domains/integration/agentcompat/internal/scenario/nat_test.go
T
2026-07-20 04:51:20 +00:00

155 lines
4.5 KiB
Go

//go:build linux && agentcompat
package scenario
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"testing"
"time"
"github.com/nezhahq/nezha/integration/agentcompat/internal/fixture"
"github.com/stretchr/testify/require"
)
func TestNAT_HTTPRequestObservesPeerWriteCloseAfterDeclaredBody(t *testing.T) {
// Given
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, listener.Close()) })
serverDone := make(chan error, 1)
go func() {
connection, acceptErr := listener.Accept()
if acceptErr != nil {
serverDone <- acceptErr
return
}
defer connection.Close()
request, readErr := http.ReadRequest(bufio.NewReader(connection))
if readErr != nil {
serverDone <- readErr
return
}
_, readErr = io.Copy(io.Discard, request.Body)
if closeErr := request.Body.Close(); readErr == nil {
readErr = closeErr
}
if readErr != nil {
serverDone <- readErr
return
}
body := "closed"
if _, writeErr := fmt.Fprintf(connection, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s%s", len(body), body, io.EOF.Error()); writeErr != nil {
serverDone <- writeErr
return
}
tcpConnection, ok := connection.(*net.TCPConn)
if !ok {
serverDone <- errors.New("test listener did not accept TCP connection")
return
}
serverDone <- tcpConnection.CloseWrite()
}()
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
// When
response, err := natHTTPRequest(ctx, natHTTPRequestSpec{Endpoint: listener.Addr().String(), Host: natTestDomain, Method: http.MethodGet, Path: "/"})
// Then
require.NoError(t, err)
require.Equal(t, http.StatusOK, response.Status)
require.Equal(t, "closed", response.Body)
require.Equal(t, io.EOF.Error(), response.LegacyCloseMarker)
require.True(t, response.PeerWriteClosed)
require.NoError(t, <-serverDone)
}
func TestNAT_ParseResponsePreservesExactRequestFields(t *testing.T) {
// Given
body := natExpectedBody(natHTTPRequestSpec{Host: natTestDomain, Method: http.MethodPatch, Path: "/nat?case=ordinary", Body: "ordinary"})
// When
record, err := parseNATResponse(body)
// Then
require.NoError(t, err)
require.Equal(t, http.MethodPatch, record.Method)
require.Equal(t, "/nat?case=ordinary", record.Path)
require.Equal(t, natTestDomain, record.Host)
require.Equal(t, "fixture", record.HeaderValue)
require.Equal(t, []byte("ordinary"), record.Body)
}
func TestNAT_ParseResponseRejectsMissingEvidence(t *testing.T) {
// Given
body := "method=GET\npath=/\n"
// When
_, err := parseNATResponse(body)
// Then
require.Error(t, err)
}
func TestNAT_ExactRequestObservedRejectsMismatchedHalfCloseEvidence(t *testing.T) {
// Given
request := natHTTPRequestSpec{Host: natHalfCloseTestDomain, Method: http.MethodPost, Path: "/nat?case=half-close", Body: "half-closed"}
exact := fixture.NATEchoRecord{Method: request.Method, Path: request.Path, Host: request.Host, HeaderValue: natEchoHeaderValue, Body: []byte(request.Body)}
mismatched := exact
mismatched.Host = natTestDomain
// When
observed := natExactRequestObserved(request, exact, mismatched)
// Then
require.False(t, observed)
}
func TestNAT_DeletedRouteObservedRequiresFallbackStatusAndBody(t *testing.T) {
// Given
request := natHTTPRequestSpec{Host: natTestDomain, Method: http.MethodGet, Path: "/"}
// When
observed := natDeletedRouteObserved(natRawResponse{Status: http.StatusOK, Body: "dashboard fallback"}, nil, request)
echoObserved := natDeletedRouteObserved(natRawResponse{Status: http.StatusOK, Body: natExpectedBody(request)}, nil, request)
rejectedObserved := natDeletedRouteObserved(natRawResponse{Status: http.StatusNotFound, Body: "not found"}, nil, request)
// Then
require.True(t, observed)
require.False(t, echoObserved)
require.False(t, rejectedObserved)
}
func TestNAT_FinishReturnsTypedFailedAssertion(t *testing.T) {
// Given
assertions := NewAssertionSet()
assertions.Record("deleted profile no longer routes", false, "backend connection observed")
// When
result, err := finishNAT(assertions, nil)
// Then
require.EqualError(t, err, "deleted profile no longer routes: backend connection observed")
require.Equal(t, "nat", result.Name)
require.False(t, result.Passed)
require.False(t, result.CleanupOK)
}
func TestNAT_FinishPreservesRuntimeError(t *testing.T) {
// Given
runtimeErr := errors.New("NAT runtime failed")
// When
result, err := finishNAT(NewAssertionSet(), runtimeErr)
// Then
require.ErrorIs(t, err, runtimeErr)
require.Equal(t, "NAT runtime failed", result.Error)
}