chore: 并入 iotlcm-worker-svr 历史到 worker-svr/(原提交 96f0f4e)

This commit is contained in:
MiFaZhan
2026-11-02 13:28:03 +08:00
422 changed files with 80993 additions and 0 deletions
@@ -0,0 +1,226 @@
package httpapi
import (
"context"
"errors"
"io"
"net"
"net/http"
"sync"
"testing"
"time"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/fullsync/contract"
)
func awaitServer(t *testing.T, done <-chan error) error {
t.Helper()
select {
case err := <-done:
return err
case <-time.After(5 * time.Second):
t.Fatal("server did not stop")
return nil
}
}
func startTestServer(t *testing.T, deps Dependencies, timeout time.Duration) (*Server, context.CancelFunc, <-chan error) {
t.Helper()
server, err := NewServer(Config{Addr: "127.0.0.1:0", BearerToken: testToken, ShutdownTimeout: timeout}, deps)
if err != nil {
t.Fatal(err)
}
if server.Addr() != nil {
t.Fatal("address before Start")
}
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
if err := server.Start(ctx); err != nil {
t.Fatal(err)
}
done := make(chan error, 1)
go func() { done <- server.Wait() }()
return server, cancel, done
}
func serverRequest(server *Server) (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, "http://"+server.Addr().String()+runPath, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+testToken)
client := &http.Client{Timeout: 4 * time.Second}
return client.Do(req)
}
func TestServerStartServeAndCancel(t *testing.T) {
server, cancel, done := startTestServer(t, testDependencies(), 0)
response, err := serverRequest(server)
if err != nil {
t.Fatal(err)
}
_ = response.Body.Close()
if response.StatusCode != 404 {
t.Fatalf("status = %d", response.StatusCode)
}
if err := server.Start(context.Background()); err == nil {
t.Fatal("duplicate Start succeeded")
}
cancel()
if err := awaitServer(t, done); err != nil {
t.Fatal(err)
}
if err := server.Wait(); err != nil {
t.Fatal(err)
}
connection, err := net.DialTimeout("tcp", server.Addr().String(), time.Second)
if err == nil {
_ = connection.Close()
t.Fatal("listener still accepts after shutdown")
}
}
func TestServerGracefulShutdownDrainsActiveRequest(t *testing.T) {
var (
entered = make(chan struct{})
release = make(chan struct{})
once sync.Once
)
t.Cleanup(func() { once.Do(func() { close(release) }) })
deps := testDependencies()
deps.Runs = runFunc(func(ctx context.Context, id string) (contract.RunView, error) {
close(entered)
select {
case <-release:
return contract.RunView{Source: contract.SourceLive, Run: contract.RunMeta{RunID: id}}, nil
case <-ctx.Done():
return contract.RunView{}, ctx.Err()
}
})
server, cancel, done := startTestServer(t, deps, 2*time.Second)
requestDone := make(chan error, 1)
go func() {
response, err := serverRequest(server)
if err == nil {
_, err = io.ReadAll(response.Body)
_ = response.Body.Close()
if response.StatusCode != 200 {
err = errors.New("active request did not complete successfully")
}
}
requestDone <- err
}()
select {
case <-entered:
case <-time.After(3 * time.Second):
t.Fatal("request did not enter handler")
}
cancel()
select {
case err := <-done:
t.Fatalf("shutdown returned before draining: %v", err)
case <-time.After(50 * time.Millisecond):
}
once.Do(func() { close(release) })
if err := awaitServer(t, requestDone); err != nil {
t.Fatal(err)
}
if err := awaitServer(t, done); err != nil {
t.Fatal(err)
}
}
func TestServerShutdownDeadlineClosesActiveConnections(t *testing.T) {
var (
entered = make(chan struct{})
exited = make(chan struct{})
)
deps := testDependencies()
deps.Runs = runFunc(func(ctx context.Context, _ string) (contract.RunView, error) {
close(entered)
<-ctx.Done()
close(exited)
return contract.RunView{}, ctx.Err()
})
server, cancel, done := startTestServer(t, deps, 50*time.Millisecond)
requestDone := make(chan error, 1)
go func() {
response, err := serverRequest(server)
if response != nil {
_ = response.Body.Close()
}
requestDone <- err
}()
select {
case <-entered:
case <-time.After(3 * time.Second):
t.Fatal("request did not enter handler")
}
cancel()
if err := awaitServer(t, done); !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("shutdown error = %v", err)
}
select {
case <-exited:
case <-time.After(3 * time.Second):
t.Fatal("forced close did not cancel request context")
}
_ = awaitServer(t, requestDone)
}
func TestServerStartupFailureIsSynchronous(t *testing.T) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = listener.Close() })
server, err := NewServer(Config{Addr: listener.Addr().String(), BearerToken: testToken}, testDependencies())
if err != nil {
t.Fatal(err)
}
startErr := server.Start(context.Background())
if startErr == nil {
t.Fatal("Start did not report occupied port")
}
if server.Wait() != startErr || server.Addr() != nil {
t.Fatal("failed startup state was lost")
}
var netErr *net.OpError
if !errors.As(startErr, &netErr) {
t.Fatalf("network error cause lost: %v", startErr)
}
}
func TestServerCanceledBeforeStartAndImmediateCancellation(t *testing.T) {
server, err := NewServer(Config{Addr: "127.0.0.1:0", BearerToken: testToken}, testDependencies())
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
if err := server.Start(ctx); !errors.Is(err, context.Canceled) {
t.Fatalf("Start = %v", err)
}
if err := server.Wait(); !errors.Is(err, context.Canceled) {
t.Fatalf("Wait = %v", err)
}
// Cancellation can win the race before Serve starts accepting requests.
for i := 0; i < 10; i++ {
_, cancel, done := startTestServer(t, testDependencies(), time.Second)
cancel()
if err := awaitServer(t, done); err != nil {
t.Fatal(err)
}
}
}
func TestNewServerRejectsInvalidConfiguration(t *testing.T) {
for _, config := range []Config{
{BearerToken: testToken},
{Addr: "127.0.0.1:0", BearerToken: testToken, ShutdownTimeout: -time.Second},
} {
if _, err := NewServer(config, testDependencies()); err == nil {
t.Fatalf("accepted config %+v", config)
}
}
}