Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
208 lines
5.5 KiB
Go
208 lines
5.5 KiB
Go
package authenticated_test
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"testing"
|
|
"time"
|
|
|
|
authenticated "github.com/tutti-os/tutti/packages/device-link/authenticated"
|
|
)
|
|
|
|
func TestAuthenticatedParticipantsCarryBidirectionalStream(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
|
defer cancel()
|
|
|
|
caller, err := authenticated.NewParticipant(authenticated.ParticipantConfig{IncludeLoopback: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer caller.Close()
|
|
owner, err := authenticated.NewParticipant(authenticated.ParticipantConfig{IncludeLoopback: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer owner.Close()
|
|
|
|
callerDescription, err := caller.LocalDescription(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ownerDescription, err := owner.LocalDescription(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ownerResult := make(chan linkResult, 1)
|
|
go func() {
|
|
link, connectErr := owner.Connect(ctx, callerDescription, authenticated.RoleOwner)
|
|
ownerResult <- linkResult{link: link, err: connectErr}
|
|
}()
|
|
callerLink, err := caller.Connect(ctx, ownerDescription, authenticated.RoleCaller)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer callerLink.Close()
|
|
result := <-ownerResult
|
|
if result.err != nil {
|
|
t.Fatal(result.err)
|
|
}
|
|
defer result.link.Close()
|
|
|
|
serveDone := make(chan error, 1)
|
|
go func() {
|
|
stream, acceptErr := result.link.AcceptStream(ctx)
|
|
if acceptErr != nil {
|
|
serveDone <- acceptErr
|
|
return
|
|
}
|
|
defer stream.Close()
|
|
_, copyErr := io.Copy(stream, stream)
|
|
serveDone <- copyErr
|
|
}()
|
|
|
|
stream, err := callerLink.OpenStream(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
payload := []byte("authenticated-device-link")
|
|
if _, err := stream.Write(payload); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := make([]byte, len(payload))
|
|
if _, err := io.ReadFull(stream, got); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(got) != string(payload) {
|
|
t.Fatalf("echo = %q, want %q", got, payload)
|
|
}
|
|
if err := stream.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := <-serveDone; err != nil && err != io.EOF {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestAuthenticatedParticipantRejectsInvalidPeerBeforeConnecting(t *testing.T) {
|
|
t.Parallel()
|
|
participant, err := authenticated.NewParticipant(authenticated.ParticipantConfig{IncludeLoopback: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer participant.Close()
|
|
_, err = participant.Connect(context.Background(), authenticated.Description{
|
|
Fingerprint: "invalid", Ufrag: "ufrag", Pwd: "pwd", Candidates: []string{"candidate"},
|
|
}, authenticated.RoleCaller)
|
|
if err == nil {
|
|
t.Fatal("Connect succeeded with invalid peer fingerprint")
|
|
}
|
|
}
|
|
|
|
func TestAuthenticatedParticipantsAcceptCredentialsBeforeCandidateTrickle(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
|
defer cancel()
|
|
|
|
caller, err := authenticated.NewParticipant(authenticated.ParticipantConfig{IncludeLoopback: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer caller.Close()
|
|
owner, err := authenticated.NewParticipant(authenticated.ParticipantConfig{IncludeLoopback: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer owner.Close()
|
|
|
|
callerInitial, err := caller.StartLocalDescription()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ownerInitial, err := owner.StartLocalDescription()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if caller.LocalCandidateChanges() == nil || owner.LocalGatheringComplete() == nil {
|
|
t.Fatal("authenticated trickle signals are unavailable")
|
|
}
|
|
callerFull, err := caller.LocalDescription(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ownerFull, err := owner.LocalDescription(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
callerInitial.Candidates = nil
|
|
ownerInitial.Candidates = nil
|
|
|
|
callerResult := make(chan linkResult, 1)
|
|
ownerResult := make(chan linkResult, 1)
|
|
go func() {
|
|
link, connectErr := caller.Connect(ctx, ownerInitial, authenticated.RoleCaller)
|
|
callerResult <- linkResult{link: link, err: connectErr}
|
|
}()
|
|
go func() {
|
|
link, connectErr := owner.Connect(ctx, callerInitial, authenticated.RoleOwner)
|
|
ownerResult <- linkResult{link: link, err: connectErr}
|
|
}()
|
|
time.Sleep(50 * time.Millisecond)
|
|
if added := caller.AddRemoteCandidates(ownerFull.Candidates); added == 0 {
|
|
t.Fatal("caller accepted no trickled owner candidates")
|
|
}
|
|
if added := owner.AddRemoteCandidates(callerFull.Candidates); added == 0 {
|
|
t.Fatal("owner accepted no trickled caller candidates")
|
|
}
|
|
|
|
callerConnected, ownerConnected := <-callerResult, <-ownerResult
|
|
if callerConnected.err != nil {
|
|
t.Fatalf("caller Connect after trickle: %v", callerConnected.err)
|
|
}
|
|
if ownerConnected.err != nil {
|
|
t.Fatalf("owner Connect after trickle: %v", ownerConnected.err)
|
|
}
|
|
defer callerConnected.link.Close()
|
|
defer ownerConnected.link.Close()
|
|
}
|
|
|
|
func TestAuthenticatedParticipantRejectsInvalidCompatibleProtocol(t *testing.T) {
|
|
t.Parallel()
|
|
_, err := authenticated.NewParticipant(authenticated.ParticipantConfig{
|
|
IncludeLoopback: true,
|
|
CompatibleProtocols: []string{" "},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("NewParticipant accepted an empty compatible protocol")
|
|
}
|
|
}
|
|
|
|
func TestAuthenticatedParticipantDoneRemainsObservableAfterClose(t *testing.T) {
|
|
t.Parallel()
|
|
participant, err := authenticated.NewParticipant(authenticated.ParticipantConfig{IncludeLoopback: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
done := participant.Done()
|
|
if done == nil {
|
|
t.Fatal("Done returned a nil channel")
|
|
}
|
|
if err := participant.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if participant.Done() != done {
|
|
t.Fatal("Done channel changed after Close")
|
|
}
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("Done did not close")
|
|
}
|
|
}
|
|
|
|
type linkResult struct {
|
|
link *authenticated.Link
|
|
err error
|
|
}
|