mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 17:50:12 +00:00
134 lines
5.0 KiB
Go
134 lines
5.0 KiB
Go
//go:build linux && agentcompat
|
|
|
|
package scenario
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/nezhahq/nezha/integration/agentcompat/internal/client"
|
|
)
|
|
|
|
type terminalDeadlineProbe struct {
|
|
writeDeadline time.Time
|
|
}
|
|
|
|
func (probe *terminalDeadlineProbe) WriteFrame(ctx context.Context, _ client.Frame) error {
|
|
probe.writeDeadline, _ = ctx.Deadline()
|
|
return context.DeadlineExceeded
|
|
}
|
|
|
|
func (*terminalDeadlineProbe) ReadFrame(context.Context) (client.Frame, error) {
|
|
return client.Frame{}, errors.New("read must not run after blocked write")
|
|
}
|
|
|
|
func TestTerminalOutputReader_ReturnsMarkerAndCloseEvidence(t *testing.T) {
|
|
frames := make(chan client.Frame, 1)
|
|
frames <- client.Frame{Type: client.FrameBinary, Payload: []byte("shell prompt\r\ncompat-size=43 132\r\ncompat-terminal\r\n")}
|
|
close(frames)
|
|
|
|
result, err := readTerminalOutput(context.Background(), terminalOutputReadInput{ExitSentAt: time.Unix(0, 0), Now: func() time.Time { return time.Unix(0, int64(time.Second)) }}, func(context.Context) (client.Frame, error) {
|
|
frame, ok := <-frames
|
|
if !ok {
|
|
return client.Frame{}, &client.WebSocketCloseError{Code: 1000, Text: "normal closure"}
|
|
}
|
|
return frame, nil
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.True(t, result.MarkerObserved)
|
|
require.True(t, result.StreamClosed)
|
|
require.Equal(t, 1000, result.CloseCode)
|
|
require.Equal(t, time.Second, result.CloseElapsed)
|
|
require.Contains(t, result.Output, "compat-terminal")
|
|
}
|
|
|
|
func TestTerminalOutputReader_ObservesRequestedPTYSize(t *testing.T) {
|
|
frames := make(chan client.Frame, 1)
|
|
frames <- client.Frame{Type: client.FrameBinary, Payload: []byte("compat-size=43 132\r\ncompat-terminal\r\n")}
|
|
close(frames)
|
|
|
|
result, err := readTerminalOutput(context.Background(), terminalOutputReadInput{ExitSentAt: time.Unix(0, 0), Now: func() time.Time { return time.Unix(0, int64(time.Second)) }}, func(context.Context) (client.Frame, error) {
|
|
frame, ok := <-frames
|
|
if !ok {
|
|
return client.Frame{}, &client.WebSocketCloseError{Code: 1000, Text: "normal closure"}
|
|
}
|
|
return frame, nil
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.True(t, result.SizeObserved)
|
|
require.Equal(t, uint32(43), result.Rows)
|
|
require.Equal(t, uint32(132), result.Cols)
|
|
}
|
|
|
|
func TestTerminalCommand_ReportsSizeBeforeMarkerAndExit(t *testing.T) {
|
|
require.Equal(t, "printf 'compat-size='; stty size; printf 'compat-terminal\\n'; exit\n", terminalCommand)
|
|
}
|
|
|
|
func TestTerminalCloseContract_AllowsAgentTimeoutPlusHarnessMargin(t *testing.T) {
|
|
require.True(t, terminalCloseWithinContract(terminalShutdownContract+terminalShutdownHarnessMargin))
|
|
require.False(t, terminalCloseWithinContract(terminalShutdownContract+terminalShutdownHarnessMargin+time.Nanosecond))
|
|
}
|
|
|
|
func TestTerminalExit_UsesOneAbsoluteDeadlineForCommandWriteAndCloseRead(t *testing.T) {
|
|
exitSentAt := time.Unix(100, 0)
|
|
probe := &terminalDeadlineProbe{}
|
|
|
|
_, err := executeTerminalExit(context.Background(), terminalExitInput{ExitSentAt: exitSentAt, Now: func() time.Time { return exitSentAt }}, probe)
|
|
|
|
require.ErrorIs(t, err, context.DeadlineExceeded)
|
|
require.Equal(t, exitSentAt.Add(terminalShutdownContract+terminalShutdownHarnessMargin), probe.writeDeadline)
|
|
}
|
|
|
|
func TestForeignTerminalPATScopes_UseMinimumAttachScope(t *testing.T) {
|
|
require.Equal(t, "nezha:server:exec", terminalAttachPATScope)
|
|
}
|
|
|
|
func TestTerminalOutputReader_RejectsNonCloseErrorAfterMarker(t *testing.T) {
|
|
reads := 0
|
|
|
|
result, err := readTerminalOutput(context.Background(), terminalOutputReadInput{ExitSentAt: time.Unix(0, 0), Now: func() time.Time { return time.Unix(0, int64(time.Second)) }}, func(context.Context) (client.Frame, error) {
|
|
reads++
|
|
if reads == 1 {
|
|
return client.Frame{Type: client.FrameBinary, Payload: []byte("compat-size=43 132\r\ncompat-terminal\r\n")}, nil
|
|
}
|
|
return client.Frame{}, context.DeadlineExceeded
|
|
})
|
|
|
|
require.ErrorIs(t, err, context.DeadlineExceeded)
|
|
require.True(t, result.MarkerObserved)
|
|
require.False(t, result.StreamClosed)
|
|
}
|
|
|
|
func TestTerminalOutputReader_RejectsProtocolCloseAfterMarker(t *testing.T) {
|
|
reads := 0
|
|
|
|
result, err := readTerminalOutput(context.Background(), terminalOutputReadInput{ExitSentAt: time.Unix(0, 0), Now: func() time.Time { return time.Unix(0, int64(time.Second)) }}, func(context.Context) (client.Frame, error) {
|
|
reads++
|
|
if reads == 1 {
|
|
return client.Frame{Type: client.FrameBinary, Payload: []byte("compat-size=43 132\r\ncompat-terminal\r\n")}, nil
|
|
}
|
|
return client.Frame{}, &client.WebSocketCloseError{Code: 1002, Text: "protocol error"}
|
|
})
|
|
|
|
var closeError *client.WebSocketCloseError
|
|
require.ErrorAs(t, err, &closeError)
|
|
require.Equal(t, 1002, result.CloseCode)
|
|
require.True(t, result.MarkerObserved)
|
|
require.False(t, result.StreamClosed)
|
|
}
|
|
|
|
func TestTerminalResizeFrame_UsesAgentWireContract(t *testing.T) {
|
|
frame, err := terminalResizeFrame(132, 43)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, client.FrameBinary, frame.Type)
|
|
require.Equal(t, byte(1), frame.Payload[0])
|
|
require.JSONEq(t, `{"Cols":132,"Rows":43}`, string(frame.Payload[1:]))
|
|
}
|