feat: implement OAuth Client ID Metadata Document (#1525) (#1526)

Co-authored-by: Elias Schneider <login@eliasschneider.com>
This commit is contained in:
Jean-François Roy
2026-08-02 15:05:39 +00:00
committed by GitHub
co-authored by Elias Schneider
parent 7c55bdf115
commit 1934efa84c
67 changed files with 2311 additions and 217 deletions
+49 -5
View File
@@ -6,12 +6,16 @@ package testing
import (
"context"
"crypto/tls"
"errors"
"net"
"testing"
"time"
"github.com/italypaleale/francis/components/standalone"
"github.com/italypaleale/francis/host/local"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
"github.com/stretchr/testify/require"
)
@@ -26,8 +30,9 @@ const testActorHostPSK = "pocket-id-test-actor-host-psk-32bytes"
func NewActorHostForTest(t *testing.T, register func(t *testing.T, h *local.Host)) *local.Host {
t.Helper()
address := freeLoopbackUDPAddr(t)
hostOpts := []local.HostOption{
local.WithAddress(freeLoopbackAddr(t)),
local.WithAddress(address),
local.WithRuntimePSKs([]byte(testActorHostPSK)),
local.WithStandaloneMemoryProvider(standalone.StandaloneMemoryOptions{}),
local.WithShutdownGracePeriod(time.Second),
@@ -62,19 +67,58 @@ func NewActorHostForTest(t *testing.T, register func(t *testing.T, h *local.Host
t.Fatal("timed out waiting for the actor host to become ready")
}
// Francis signals host readiness before starting the peer server, so wait for a remote TLS response before a fast test can trigger cleanup
waitForActorHostPeerServer(t, address, errCh)
return h
}
// freeLoopbackAddr reserves a free loopback port and returns its address
// waitForActorHostPeerServer waits until the WebTransport listener has passed the startup point that races with shutdown
func waitForActorHostPeerServer(t *testing.T, address string, errCh <-chan error) {
t.Helper()
// The probe intentionally omits the Francis client certificate because a remote TLS rejection is enough to prove the peer server is accepting connections
//nolint:gosec
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{http3.NextProtoH3},
}
deadline := time.Now().Add(10 * time.Second)
for time.Now().Before(deadline) {
probeCtx, probeCancel := context.WithTimeout(t.Context(), 200*time.Millisecond)
conn, err := quic.DialAddr(probeCtx, address, tlsConfig, &quic.Config{})
probeCancel()
if conn != nil {
_ = conn.CloseWithError(0, "readiness probe complete")
return
}
var transportErr *quic.TransportError
if errors.As(err, &transportErr) && transportErr.Remote {
return
}
select {
case runErr := <-errCh:
t.Fatalf("actor host stopped before its peer server became ready: %v", runErr)
case <-time.After(10 * time.Millisecond):
}
}
t.Fatalf("timed out waiting for actor host peer server %s", address)
}
// freeLoopbackUDPAddr reserves a free loopback UDP port and returns its address
// The port is released before returning, so the actor host can bind it
func freeLoopbackAddr(t *testing.T) string {
func freeLoopbackUDPAddr(t *testing.T) string {
t.Helper()
var lc net.ListenConfig
lis, err := lc.Listen(t.Context(), "tcp", "127.0.0.1:0")
lis, err := lc.ListenPacket(t.Context(), "udp", "127.0.0.1:0")
require.NoError(t, err)
addr := lis.Addr().String()
addr := lis.LocalAddr().String()
err = lis.Close()
require.NoError(t, err)