183 lines
5.6 KiB
Go
183 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
omni "git.yuntuo.hk/k2safe/go-omni"
|
|
"git.yuntuo.hk/k2safe/go-omni/auth"
|
|
"git.yuntuo.hk/k2safe/go-omni/examples/reference/repository"
|
|
"git.yuntuo.hk/k2safe/go-omni/internal/testutil"
|
|
)
|
|
|
|
// networkHandler runs the same business assertions through a real TCP client.
|
|
// The application handler is only called by its configured server transport.
|
|
type networkHandler struct {
|
|
t *testing.T
|
|
client *http.Client
|
|
address, scheme, token, alpn string
|
|
proto int
|
|
requests *int
|
|
}
|
|
|
|
func (h networkHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
h.t.Helper()
|
|
req := r.Clone(r.Context())
|
|
req.RequestURI = ""
|
|
req.URL.Scheme, req.URL.Host, req.Host = h.scheme, h.address, h.address
|
|
if h.token != "" && req.Header.Get("Authorization") == "" {
|
|
req.Header.Set("Authorization", "Bearer "+h.token)
|
|
}
|
|
response, err := h.client.Do(req)
|
|
if err != nil {
|
|
h.t.Fatal(err)
|
|
}
|
|
defer response.Body.Close()
|
|
if strings.HasPrefix(req.URL.Path, "/api/v1/") {
|
|
*h.requests++
|
|
}
|
|
if response.ProtoMajor != h.proto {
|
|
h.t.Fatalf("protocol %s, want HTTP/%d", response.Proto, h.proto)
|
|
}
|
|
if h.scheme == "https" && (response.TLS == nil || len(response.TLS.VerifiedChains) == 0 || response.TLS.NegotiatedProtocol != h.alpn) {
|
|
h.t.Fatalf("TLS/ALPN verification failed: %+v", response.TLS)
|
|
}
|
|
for key, values := range response.Header {
|
|
w.Header()[key] = append([]string(nil), values...)
|
|
}
|
|
w.WriteHeader(response.StatusCode)
|
|
if _, err := io.Copy(w, response.Body); err != nil {
|
|
h.t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
type lockedLog struct {
|
|
sync.Mutex
|
|
bytes.Buffer
|
|
}
|
|
|
|
func (b *lockedLog) Write(p []byte) (int, error) {
|
|
b.Lock()
|
|
defer b.Unlock()
|
|
return b.Buffer.Write(p)
|
|
}
|
|
|
|
func (b *lockedLog) snapshot() string {
|
|
b.Lock()
|
|
defer b.Unlock()
|
|
return b.Buffer.String()
|
|
}
|
|
|
|
func TestBusinessConfiguredTransports(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name, transport string
|
|
tls bool
|
|
proto int
|
|
alpn string
|
|
}{
|
|
{"nethttp", omni.TransportNetHTTP, false, 1, ""},
|
|
{"native", omni.TransportNative, false, 1, ""},
|
|
{"nethttpTLS", omni.TransportNetHTTP, true, 2, "h2"},
|
|
{"nativeTLS", omni.TransportNative, true, 1, "http/1.1"},
|
|
{"http2TLS", omni.TransportHTTP2, true, 2, "h2"},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
cert, key, tlsClient := testutil.CertificateFiles(t)
|
|
t.Setenv("REFERENCE_TEST_TRANSPORT", test.transport)
|
|
t.Setenv("REFERENCE_TEST_SHUTDOWN_TIMEOUT", "3s")
|
|
t.Setenv("REFERENCE_TEST_TLS_CERT_FILE", "")
|
|
t.Setenv("REFERENCE_TEST_TLS_KEY_FILE", "")
|
|
if test.tls {
|
|
t.Setenv("REFERENCE_TEST_TLS_CERT_FILE", cert)
|
|
t.Setenv("REFERENCE_TEST_TLS_KEY_FILE", key)
|
|
}
|
|
settings, err := omni.LoadConfig("REFERENCE_TEST_")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var logs lockedLog
|
|
logger := testLogger(&logs)
|
|
application := newProtectedApp(repository.NewMemoryUsers(), logger, auth.StaticAuthenticator{
|
|
"admin": {UserID: "admin", Roles: []string{"admin"}},
|
|
"user": {UserID: "user", Roles: []string{"user"}},
|
|
}, omni.WithConfig(settings))
|
|
application.OnShutdown(logger.Close)
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
done := make(chan error, 1)
|
|
go func() { done <- application.ServeContext(ctx, listener) }()
|
|
t.Cleanup(func() {
|
|
cancel()
|
|
select {
|
|
case err := <-done:
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
t.Error("business server did not stop")
|
|
listener.Close()
|
|
}
|
|
})
|
|
client := &http.Client{Timeout: 5 * time.Second, Transport: &http.Transport{TLSClientConfig: tlsClient, ForceAttemptHTTP2: true}}
|
|
defer client.CloseIdleConnections()
|
|
scheme := "http"
|
|
if test.tls {
|
|
scheme = "https"
|
|
}
|
|
requests := 0
|
|
network := networkHandler{t: t, client: client, address: listener.Addr().String(), scheme: scheme, proto: test.proto, alpn: test.alpn, requests: &requests}
|
|
request(t, network, "POST", "/api/v1/users", `{"name":"Alice","email":"alice@example.com"}`, 401, "UNAUTHENTICATED")
|
|
requestWithAuthorization(t, network, "DELETE", "/api/v1/users/1", "", "Bearer user", 403, "FORBIDDEN")
|
|
network.token = "admin"
|
|
checkUserCRUD(t, network)
|
|
request(t, network, "POST", "/api/v1/users", `{"name":`, 400, "INVALID_BODY")
|
|
count := 0
|
|
for _, line := range strings.Split(strings.TrimSpace(logs.snapshot()), "\n") {
|
|
var record map[string]any
|
|
if err := json.Unmarshal([]byte(line), &record); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if record["requestId"] == "reference-test" {
|
|
count++
|
|
}
|
|
}
|
|
if count < requests {
|
|
t.Fatalf("missing correlated business logs: %d", count)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReferenceAddressCompatibility(t *testing.T) {
|
|
t.Setenv("GO_OMNI_TRANSPORT", "native")
|
|
t.Setenv("GO_OMNI_TLS_CERT_FILE", "")
|
|
t.Setenv("GO_OMNI_TLS_KEY_FILE", "")
|
|
t.Setenv("GO_OMNI_HTTP_ADDR", "")
|
|
t.Setenv("GO_OMNI_ADDR", "")
|
|
settings, err := referenceConfig()
|
|
if err != nil || settings.Address != "127.0.0.1:8080" || settings.Transport != "native" {
|
|
t.Fatalf("default: %+v %v", settings, err)
|
|
}
|
|
t.Setenv("GO_OMNI_ADDR", "127.0.0.1:8081")
|
|
settings, err = referenceConfig()
|
|
if err != nil || settings.Address != "127.0.0.1:8081" {
|
|
t.Fatalf("legacy: %+v %v", settings, err)
|
|
}
|
|
t.Setenv("GO_OMNI_HTTP_ADDR", "127.0.0.1:8082")
|
|
settings, err = referenceConfig()
|
|
if err != nil || settings.Address != "127.0.0.1:8082" {
|
|
t.Fatalf("standard: %+v %v", settings, err)
|
|
}
|
|
}
|