26f897c1ec
release / release-please (push) Failing after 1m49s
docs / build (push) Failing after 6m34s
release / build-and-upload (arm64, linux) (push) Has been cancelled
release / build-and-upload (arm64, windows) (push) Has been cancelled
release / build-darwin (amd64, darwin) (push) Has been cancelled
release / checksums (push) Has been cancelled
release / finalize (push) Has been cancelled
release / build-darwin (arm64, darwin) (push) Has been cancelled
release / build-and-upload (amd64, linux) (push) Has been cancelled
release / build-and-upload (amd64, windows) (push) Has been cancelled
docs / deploy (push) Has been cancelled
196 lines
5.4 KiB
Go
196 lines
5.4 KiB
Go
package ipc_test
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/kunchenguid/no-mistakes/internal/ipc"
|
|
)
|
|
|
|
func TestSubscribeServerError(t *testing.T) {
|
|
sock := socketPath(t)
|
|
srv := startServer(t, sock)
|
|
|
|
// Register subscribe as a regular handler that returns an error.
|
|
// The client's Subscribe reads this error response and should return it.
|
|
srv.Handle(ipc.MethodSubscribe, func(_ context.Context, _ json.RawMessage) (interface{}, error) {
|
|
return nil, fmt.Errorf("run not found")
|
|
})
|
|
|
|
_, _, err := ipc.Subscribe(sock, &ipc.SubscribeParams{RunID: "nonexistent"})
|
|
if err == nil {
|
|
t.Fatal("expected error from Subscribe")
|
|
}
|
|
if !strings.Contains(err.Error(), "run not found") {
|
|
t.Errorf("error = %q, want to contain 'run not found'", err)
|
|
}
|
|
}
|
|
|
|
func TestSubscribeMalformedEvent(t *testing.T) {
|
|
sock := socketPath(t)
|
|
srv := startServer(t, sock)
|
|
|
|
// Stream handler sends one valid event, one malformed JSON, one valid event.
|
|
srv.HandleStream(ipc.MethodSubscribe, func(_ context.Context, _ json.RawMessage, send func(interface{}) error) error {
|
|
s1 := "first"
|
|
if err := send(ipc.Event{Type: ipc.EventRunUpdated, RunID: "r1", Status: &s1}); err != nil {
|
|
return err
|
|
}
|
|
// Send raw malformed JSON by encoding a special string that the encoder wraps.
|
|
// We need to write raw bytes — use send with a type that produces invalid Event JSON.
|
|
// Actually, the send function calls encoder.Encode which always produces valid JSON.
|
|
// So we need to write directly to the connection. Instead, we'll test this via a raw socket.
|
|
return nil
|
|
})
|
|
|
|
// This approach won't work for malformed events since send always produces valid JSON.
|
|
// Use a raw socket instead on a separate path to avoid a race where the old
|
|
// listener's Close unlinks the socket after the new listener creates it.
|
|
srv.Close()
|
|
|
|
// Start a fresh minimal server with raw socket control.
|
|
sock = socketPath(t)
|
|
ln := rawListen(t, sock)
|
|
defer ln.Close()
|
|
|
|
go func() {
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
scanner := bufio.NewScanner(conn)
|
|
if !scanner.Scan() {
|
|
return
|
|
}
|
|
// Read the subscribe request, send OK response.
|
|
var req ipc.Request
|
|
json.Unmarshal(scanner.Bytes(), &req)
|
|
|
|
enc := json.NewEncoder(conn)
|
|
okResp := ipc.Response{JSONRPC: "2.0", ID: req.ID}
|
|
okResult, _ := json.Marshal(map[string]bool{"ok": true})
|
|
okResp.Result = okResult
|
|
enc.Encode(okResp)
|
|
|
|
// Send valid event.
|
|
s1 := "first"
|
|
enc.Encode(ipc.Event{Type: ipc.EventRunUpdated, RunID: "r1", Status: &s1})
|
|
|
|
// Send malformed JSON.
|
|
conn.Write([]byte("{bad json}\n"))
|
|
|
|
// Send another valid event.
|
|
s2 := "second"
|
|
enc.Encode(ipc.Event{Type: ipc.EventRunUpdated, RunID: "r1", Status: &s2})
|
|
}()
|
|
|
|
ch, cancel, err := ipc.Subscribe(sock, &ipc.SubscribeParams{RunID: "r1"})
|
|
if err != nil {
|
|
t.Fatalf("subscribe: %v", err)
|
|
}
|
|
defer cancel()
|
|
|
|
var events []ipc.Event
|
|
for event := range ch {
|
|
events = append(events, event)
|
|
}
|
|
|
|
// Malformed event should be skipped — we should get 2 events, not 3.
|
|
if len(events) != 2 {
|
|
t.Fatalf("got %d events, want 2 (malformed should be skipped)", len(events))
|
|
}
|
|
if events[0].Status == nil || *events[0].Status != "first" {
|
|
t.Errorf("event 0 status = %v, want 'first'", events[0].Status)
|
|
}
|
|
if events[1].Status == nil || *events[1].Status != "second" {
|
|
t.Errorf("event 1 status = %v, want 'second'", events[1].Status)
|
|
}
|
|
}
|
|
|
|
func TestSubscribeConnectionClosedBeforeResponse(t *testing.T) {
|
|
sock := socketPath(t)
|
|
os.Remove(sock)
|
|
|
|
// Start a raw server that closes connection immediately after accept.
|
|
ln := rawListen(t, sock)
|
|
defer ln.Close()
|
|
|
|
go func() {
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
// Read the request then close without responding.
|
|
scanner := bufio.NewScanner(conn)
|
|
scanner.Scan() // consume the request
|
|
conn.Close()
|
|
}()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
_, _, err := ipc.Subscribe(sock, &ipc.SubscribeParams{RunID: "r1"})
|
|
if err == nil {
|
|
t.Fatal("expected error when connection closed before response")
|
|
}
|
|
if !strings.Contains(err.Error(), "connection closed") {
|
|
t.Errorf("error = %q, want to contain 'connection closed'", err)
|
|
}
|
|
}
|
|
|
|
func TestSubscribeClient(t *testing.T) {
|
|
sock := socketPath(t)
|
|
srv := startServer(t, sock)
|
|
|
|
// Set up a stream handler that sends 3 events.
|
|
srv.HandleStream(ipc.MethodSubscribe, func(_ context.Context, raw json.RawMessage, send func(interface{}) error) error {
|
|
var p ipc.SubscribeParams
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
return err
|
|
}
|
|
for i := 0; i < 3; i++ {
|
|
status := fmt.Sprintf("event-%d", i)
|
|
event := ipc.Event{
|
|
Type: ipc.EventRunUpdated,
|
|
RunID: p.RunID,
|
|
Status: &status,
|
|
}
|
|
if err := send(event); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil // handler returns → connection closes → channel closes
|
|
})
|
|
|
|
ch, cancel, err := ipc.Subscribe(sock, &ipc.SubscribeParams{RunID: "run123"})
|
|
if err != nil {
|
|
t.Fatalf("subscribe: %v", err)
|
|
}
|
|
defer cancel()
|
|
|
|
var events []ipc.Event
|
|
for event := range ch {
|
|
events = append(events, event)
|
|
}
|
|
|
|
if len(events) != 3 {
|
|
t.Fatalf("got %d events, want 3", len(events))
|
|
}
|
|
for i, event := range events {
|
|
wantStatus := fmt.Sprintf("event-%d", i)
|
|
if event.RunID != "run123" {
|
|
t.Errorf("event %d: runID=%q, want %q", i, event.RunID, "run123")
|
|
}
|
|
if event.Status == nil || *event.Status != wantStatus {
|
|
t.Errorf("event %d: status=%v, want %q", i, event.Status, wantStatus)
|
|
}
|
|
}
|
|
}
|