package api
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"time"
"tera-clone/services/tera-api/internal/adapters/mock"
"tera-clone/services/tera-api/internal/directstream"
"tera-clone/services/tera-api/internal/domain"
"tera-clone/services/tera-api/internal/portservice"
"tera-clone/services/tera-api/internal/serial"
"tera-clone/services/tera-api/internal/sessionhealth"
)
type runtimeSerialController struct { }
func ( runtimeSerialController ) States ( context . Context ) ( [ ] serial . State , error ) {
states := make ( [ ] serial . State , 0 , serial . ChannelCount )
for index := 1 ; index <= serial . ChannelCount ; index ++ {
settings := & serial . LineSettings { BaudRate : 9600 , DataBits : 8 , Parity : "none" , StopBits : 1 , FlowControl : "none" }
states = append ( states , serial . State {
Channel : fmt . Sprintf ( "CH%02d" , index ) , Device : fmt . Sprintf ( "/dev/ttyWCH%d" , index + 3 ) ,
HardwareProfile : "fet3568j-c" , MappingVersion : "test-v1" , Available : true ,
ProfileApplied : true , LineSettings : settings ,
} )
}
return states , nil
}
func ( runtimeSerialController ) LinkStates ( context . Context ) ( [ ] serial . LinkState , error ) {
links := make ( [ ] serial . LinkState , 0 , serial . ChannelCount )
for index := 1 ; index <= serial . ChannelCount ; index ++ {
links = append ( links , serial . LinkState {
Channel : fmt . Sprintf ( "CH%02d" , index ) , State : "unknown" , Signal : "unknown" ,
Message : "physical DCD/DSR signal is not board-validated" ,
} )
}
return links , nil
}
func ( runtimeSerialController ) CRProbeStates ( context . Context ) ( [ ] serial . CRProbeState , error ) {
confirmedAt := fixedClock ( ) . UTC ( )
return [ ] serial . CRProbeState { {
Channel : "CH01" , Enabled : true , State : serial . CRProbeStateResponded ,
Method : "cr" , ResponseBytes : 7 , ConsecutiveResponses : 1 , Up : true , EverUp : true ,
LastConfirmedAt : & confirmedAt , Message : "remote serial endpoint response confirmed" ,
} , {
Channel : "CH08" , Enabled : true , State : serial . CRProbeStateResponded ,
Method : "cr" , ResponseBytes : 7 , ConsecutiveResponses : 2 , Up : true , EverUp : true ,
LastConfirmedAt : & confirmedAt , Message : "remote serial endpoint response confirmed" ,
} , {
Channel : "CH20" , Enabled : true , State : serial . CRProbeStateResponded ,
Method : "cr" , ResponseBytes : 7 , ConsecutiveResponses : 1 , Up : true , EverUp : true ,
LastConfirmedAt : & confirmedAt , Message : "remote serial endpoint response confirmed" ,
} } , nil
}
func ( runtimeSerialController ) Apply ( context . Context , [ ] serial . ChannelConfig ) ( [ ] serial . State , error ) {
return nil , nil
}
type runtimeSessionController struct { }
func ( runtimeSessionController ) Status ( context . Context ) ( sessionhealth . Status , error ) {
return sessionhealth . Status {
Status : "ok" ,
Broker : directstream . BrokerSnapshot {
TotalAttachments : 2 , ActiveChannels : 1 ,
Channels : [ ] directstream . ChannelSnapshot { {
Channel : "CH01" , AttachmentCount : 2 , MaxAttachments : 4 ,
WriterSessionID : "session-real" , QueuedBytes : 7 ,
} } ,
} ,
Serial : serial . StreamSnapshot {
ActiveLeases : 1 ,
Channels : [ ] serial . StreamChannelSnapshot { { Channel : "CH01" , Active : true , RXBytes : 11 , TXBytes : 13 } } ,
} ,
} , nil
}
type runtimePortServiceController struct { }
func ( runtimePortServiceController ) Apply ( context . Context , [ ] domain . Port ) error { return nil }
func ( runtimePortServiceController ) Snapshot ( ) portservice . Snapshot {
return portservice . Snapshot { Services : [ ] portservice . ServiceSnapshot { {
Channel : "CH20" , Operation : 1 , Mode : "reverse_ssh" , Port : 2423 ,
State : "listening" , ActiveClients : 1 ,
} } }
}
type countingRuntimeSerialController struct {
runtimeSerialController
mu sync . Mutex
stateCalls int
}
func ( c * countingRuntimeSerialController ) States ( ctx context . Context ) ( [ ] serial . State , error ) {
c . mu . Lock ( )
c . stateCalls ++
c . mu . Unlock ( )
return c . runtimeSerialController . States ( ctx )
}
func ( c * countingRuntimeSerialController ) calls ( ) int {
c . mu . Lock ( )
defer c . mu . Unlock ( )
return c . stateCalls
}
func TestPortAndSystemStatusUseActualSerialRuntime ( t * testing . T ) {
handler := NewRouterWithOptions ( mock . NewStore ( fixedClock ) , RouterOptions {
SerialController : runtimeSerialController { } , SessionController : runtimeSessionController { } ,
} )
portsResponse := httptest . NewRecorder ( )
portsRequest := httptest . NewRequest ( http . MethodGet , "/api/v1/ports?limit=48" , nil )
portsRequest . Header . Set ( "X-Mock-Actor" , "mock-admin" )
handler . ServeHTTP ( portsResponse , portsRequest )
if portsResponse . Code != http . StatusOK {
t . Fatalf ( "ports status=%d body=%s" , portsResponse . Code , portsResponse . Body . String ( ) )
}
var ports [ ] domain . Port
if err := json . Unmarshal ( portsResponse . Body . Bytes ( ) , & ports ) ; err != nil {
t . Fatal ( err )
}
if len ( ports ) != serial . ChannelCount || ports [ 0 ] . Status != "session" || ports [ 0 ] . ConnectedUsers != 2 || ports [ 0 ] . Writer != "session-real" {
t . Fatalf ( "CH01 = %#v" , ports [ 0 ] )
}
if ports [ 0 ] . Runtime == nil || ! ports [ 0 ] . Runtime . ProfileApplied || ports [ 0 ] . Runtime . RXBytes != 11 || ports [ 0 ] . Runtime . TXBytes != 13 || ports [ 0 ] . Runtime . LinkAvailable {
t . Fatalf ( "CH01 runtime = %#v" , ports [ 0 ] . Runtime )
}
if ports [ 1 ] . Status != "unknown" {
t . Fatalf ( "CH02 status = %q, want unknown" , ports [ 1 ] . Status )
}
if ports [ 7 ] . Runtime == nil || ports [ 7 ] . Runtime . CRProbeState != serial . CRProbeStateResponded || ports [ 7 ] . Runtime . CRProbeBytes != 7 ||
! ports [ 7 ] . Runtime . CRProbeUp || ! ports [ 7 ] . Runtime . CRProbeEverUp || ports [ 7 ] . Runtime . CRProbeResponses != 2 || ports [ 7 ] . Runtime . CRProbeConfirmedAt == nil {
t . Fatalf ( "CH08 CR runtime = %#v" , ports [ 7 ] . Runtime )
}
statusResponse := httptest . NewRecorder ( )
statusRequest := httptest . NewRequest ( http . MethodGet , "/api/v1/system/status" , nil )
statusRequest . Header . Set ( "X-Mock-Actor" , "mock-admin" )
handler . ServeHTTP ( statusResponse , statusRequest )
var status domain . SystemStatus
if err := json . Unmarshal ( statusResponse . Body . Bytes ( ) , & status ) ; err != nil {
t . Fatal ( err )
}
if status . SerialPortCount != serial . ChannelCount || status . ActiveSessions != 2 {
t . Fatalf ( "system status = %#v" , status )
}
servicesResponse := httptest . NewRecorder ( )
servicesRequest := httptest . NewRequest ( http . MethodGet , "/api/v1/system/services?limit=100" , nil )
servicesRequest . Header . Set ( "X-Mock-Actor" , "mock-admin" )
handler . ServeHTTP ( servicesResponse , servicesRequest )
var services [ ] domain . ServiceHealth
if err := json . Unmarshal ( servicesResponse . Body . Bytes ( ) , & services ) ; err != nil {
t . Fatal ( err )
}
statusByService := make ( map [ string ] string , len ( services ) )
for _ , service := range services {
statusByService [ service . Service ] = service . Status
}
if statusByService [ "tera-seriald" ] != "ok" || statusByService [ "tera-sessiond" ] != "ok" || statusByService [ "tera-hwd" ] != "degraded" {
t . Fatalf ( "service statuses = %#v" , statusByService )
}
}
func TestSerialRuntimeSnapshotIsSharedForShortBurst ( t * testing . T ) {
controller := & countingRuntimeSerialController { }
handler := NewRouterWithOptions ( mock . NewStore ( fixedClock ) , RouterOptions {
SerialController : controller , SessionController : runtimeSessionController { } ,
} )
requestPorts := func ( ) {
t . Helper ( )
response := httptest . NewRecorder ( )
request := httptest . NewRequest ( http . MethodGet , "/api/v1/ports?limit=48" , nil )
request . Header . Set ( "X-Mock-Actor" , "mock-admin" )
handler . ServeHTTP ( response , request )
if response . Code != http . StatusOK {
t . Fatalf ( "ports status=%d body=%s" , response . Code , response . Body . String ( ) )
}
}
requestPorts ( )
requestPorts ( )
if calls := controller . calls ( ) ; calls != 1 {
t . Fatalf ( "serial States calls=%d, want 1 for a short request burst" , calls )
}
time . Sleep ( runtimeStatusCacheTTL + 50 * time . Millisecond )
requestPorts ( )
if calls := controller . calls ( ) ; calls != 2 {
t . Fatalf ( "serial States calls=%d, want 2 after cache expiry" , calls )
}
}
func TestReverseSSHClientMarksPortAsSession ( t * testing . T ) {
handler := NewRouterWithOptions ( mock . NewStore ( fixedClock ) , RouterOptions {
SerialController : runtimeSerialController { } , SessionController : runtimeSessionController { } ,
PortServiceController : runtimePortServiceController { } ,
} )
response := httptest . NewRecorder ( )
request := httptest . NewRequest ( http . MethodGet , "/api/v1/ports?limit=48" , nil )
request . Header . Set ( "X-Mock-Actor" , "mock-admin" )
handler . ServeHTTP ( response , request )
if response . Code != http . StatusOK {
t . Fatalf ( "ports status=%d body=%s" , response . Code , response . Body . String ( ) )
}
var ports [ ] domain . Port
if err := json . Unmarshal ( response . Body . Bytes ( ) , & ports ) ; err != nil {
t . Fatal ( err )
}
port := ports [ 19 ]
if port . Channel != "CH20" || port . Status != "session" || port . ConnectedUsers != 1 {
t . Fatalf ( "CH20 = %#v" , port )
}
if port . Runtime == nil || ! port . Runtime . SessionActive || len ( port . Runtime . Operations ) != 1 || port . Runtime . Operations [ 0 ] . ActiveClients != 1 {
t . Fatalf ( "CH20 runtime = %#v" , port . Runtime )
}
}
func TestSharedBrokerDoesNotCountOperationClientsTwice ( t * testing . T ) {
router := & Router { portServices : runtimePortServiceController { } }
runtime := serialRuntimeSnapshot { }
runtime . session . Broker . SharedPortSessions = true
runtime . session . Broker . Channels = [ ] directstream . ChannelSnapshot { { Channel : "CH20" , AttachmentCount : 3 , MaxAttachments : 4 } }
ports := router . overlayPortRuntime ( [ ] domain . Port { { Channel : "CH20" } } , runtime )
if ports [ 0 ] . ConnectedUsers != 3 {
t . Fatalf ( "connected users = %d" , ports [ 0 ] . ConnectedUsers )
}
}
func TestPortRuntimeStatusEndpointReturnsCompactLiveState ( t * testing . T ) {
handler := NewRouterWithOptions ( mock . NewStore ( fixedClock ) , RouterOptions {
SerialController : runtimeSerialController { } , SessionController : runtimeSessionController { } ,
PortServiceController : runtimePortServiceController { } ,
} )
response := httptest . NewRecorder ( )
request := httptest . NewRequest ( http . MethodGet , "/api/v1/ports/runtime-status" , nil )
request . Header . Set ( "X-Mock-Actor" , "mock-admin" )
handler . ServeHTTP ( response , request )
if response . Code != http . StatusOK {
t . Fatalf ( "runtime status=%d body=%s" , response . Code , response . Body . String ( ) )
}
var ports [ ] compactPortRuntimeStatus
if err := json . Unmarshal ( response . Body . Bytes ( ) , & ports ) ; err != nil {
t . Fatal ( err )
}
if len ( ports ) != serial . ChannelCount {
t . Fatalf ( "ports=%d, want %d" , len ( ports ) , serial . ChannelCount )
}
if ports [ 0 ] . Channel != "CH01" || ports [ 0 ] . Status != "session" || ! ports [ 0 ] . Runtime . SessionActive {
t . Fatalf ( "CH01 = %#v" , ports [ 0 ] )
}
if ports [ 7 ] . Status != "up" || ! ports [ 7 ] . Runtime . CRProbeUp {
t . Fatalf ( "CH08 = %#v" , ports [ 7 ] )
}
if ports [ 19 ] . Status != "session" || ports [ 19 ] . ConnectedUsers != 1 {
t . Fatalf ( "CH20 = %#v" , ports [ 19 ] )
}
if bytes := response . Body . Bytes ( ) ; len ( bytes ) >= 30000 || string ( bytes ) == "" {
t . Fatalf ( "compact response size=%d" , len ( bytes ) )
}
if strings . Contains ( response . Body . String ( ) , ` "operation_1" ` ) || strings . Contains ( response . Body . String ( ) , ` "baud_rate" ` ) {
t . Fatalf ( "compact response contains port configuration: %s" , response . Body . String ( ) )
}
}
func TestRuntimePortStatusRequiresUpBeforeDownAndPrioritizesSession ( t * testing . T ) {
tests := [ ] struct {
name string
runtime * domain . PortRuntime
want string
} {
{ name : "boot" , runtime : & domain . PortRuntime { } , want : "unknown" } ,
{ name : "unconfirmed miss" , runtime : & domain . PortRuntime { CRProbeState : serial . CRProbeStateNoResponse } , want : "unknown" } ,
{ name : "confirmed response" , runtime : & domain . PortRuntime { CRProbeEnabled : true , CRProbeUp : true , CRProbeEverUp : true } , want : "up" } ,
{ name : "no response is not cable down" , runtime : & domain . PortRuntime { CRProbeEnabled : true , CRProbeEverUp : true , CRProbeState : serial . CRProbeStateNoResponse } , want : "unknown" } ,
{ name : "no RX timestamp" , runtime : & domain . PortRuntime { RXAgeMillis : 0 } , want : "unknown" } ,
{ name : "confirmed hardware down" , runtime : & domain . PortRuntime { LinkAvailable : true , LinkEverUp : true , LinkState : "down" , CRProbeEnabled : true , CRProbeUp : true } , want : "down" } ,
{ name : "unknown session is not promoted" , runtime : & domain . PortRuntime { SessionActive : true } , want : "unknown" } ,
{ name : "active session" , runtime : & domain . PortRuntime { SessionActive : true , CRProbeEverUp : true , CRProbeState : serial . CRProbeStateNoResponse } , want : "session" } ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
if got := runtimePortStatus ( test . runtime ) ; got != test . want {
t . Fatalf ( "runtimePortStatus()=%q, want %q" , got , test . want )
}
} )
}
}