227 lines
5.9 KiB
Go
227 lines
5.9 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|