Files
meowlib/client/invitation_test.go
ycc 1906431061
Some checks failed
continuous-integration/drone/push Build is failing
invitation process upgrade
2026-04-02 20:26:35 +02:00

206 lines
7.5 KiB
Go

package client
import (
"os"
"testing"
"forge.redroom.link/yves/meowlib"
"github.com/stretchr/testify/assert"
)
// setupInvitationTest creates two independent identities with separate storage paths.
func setupInvitationTest(t *testing.T) (initiator *Identity, invitee *Identity, cleanup func()) {
t.Helper()
cfg := GetConfig()
cfg.IdentityFile = "inv_test_init.id"
cfg.SetMemPass("testpass")
initiator, err := CreateIdentity("initiator")
if err != nil {
t.Fatal(err)
}
invitee, err = CreateIdentity("invitee")
if err != nil {
t.Fatal(err)
}
// Give each identity a pull server UID.
srvInit, _ := CreateServerFromUrl("http://init.server/meow/")
initiator.MessageServers.StoreServer(srvInit)
srvInvitee, _ := CreateServerFromUrl("http://invitee.server/meow/")
invitee.MessageServers.StoreServer(srvInvitee)
cleanup = func() {
os.Remove("inv_test_init.id")
os.RemoveAll(cfg.StoragePath + "/" + initiator.Uuid)
os.RemoveAll(cfg.StoragePath + "/" + invitee.Uuid)
}
return initiator, invitee, cleanup
}
// TestInvitationStep1 verifies that InvitationStep1 creates a minimal peer with only
// InvitationKp set (no full keypairs yet) and returns a valid InvitationInitPayload.
func TestInvitationStep1(t *testing.T) {
initiator, _, cleanup := setupInvitationTest(t)
defer cleanup()
payload, peer, err := initiator.InvitationStep1("Alice", "Bob", []string{"http://init.server/meow/"}, "Hello Bob!")
assert.NoError(t, err)
assert.NotNil(t, payload)
assert.NotNil(t, peer)
assert.NotEmpty(t, payload.Uuid)
assert.Equal(t, "Alice", payload.Name)
assert.NotEmpty(t, payload.PublicKey)
assert.Equal(t, "Hello Bob!", payload.InvitationMessage)
// Full keypairs must NOT be set yet.
assert.Nil(t, peer.MyIdentity)
assert.Nil(t, peer.MyEncryptionKp)
assert.Nil(t, peer.MyLookupKp)
// Temp keypair must be set.
assert.NotNil(t, peer.InvitationKp)
assert.Equal(t, payload.PublicKey, peer.InvitationKp.Public)
}
// TestInvitationStep1PayloadRoundTrip verifies Compress/Decompress of InvitationInitPayload.
func TestInvitationStep1PayloadRoundTrip(t *testing.T) {
initiator, _, cleanup := setupInvitationTest(t)
defer cleanup()
payload, _, err := initiator.InvitationStep1("Alice", "Bob", nil, "test msg")
assert.NoError(t, err)
compressed, err := payload.Compress()
assert.NoError(t, err)
assert.NotEmpty(t, compressed)
restored, err := meowlib.NewInvitationInitPayloadFromCompressed(compressed)
assert.NoError(t, err)
assert.Equal(t, payload.Uuid, restored.Uuid)
assert.Equal(t, payload.Name, restored.Name)
assert.Equal(t, payload.PublicKey, restored.PublicKey)
assert.Equal(t, payload.InvitationMessage, restored.InvitationMessage)
}
// TestInvitationStep2 verifies that InvitationStep2 creates a peer with full keypairs and
// sets the initiator's temp key as both ContactEncryption and ContactLookupKey.
func TestInvitationStep2(t *testing.T) {
initiator, invitee, cleanup := setupInvitationTest(t)
defer cleanup()
payload, _, err := initiator.InvitationStep1("Alice", "Bob", nil, "Hi")
assert.NoError(t, err)
peer, err := invitee.InvitationStep2("Bob", "Alice", []string{"http://invitee.server/meow/"}, payload)
assert.NoError(t, err)
assert.NotNil(t, peer)
// Full keypairs must be set on invitee's peer.
assert.NotNil(t, peer.MyIdentity)
assert.NotNil(t, peer.MyEncryptionKp)
assert.NotNil(t, peer.MyLookupKp)
// Contact fields must point to initiator's temp key.
assert.Equal(t, payload.PublicKey, peer.ContactEncryption)
assert.Equal(t, payload.PublicKey, peer.ContactLookupKey)
assert.Equal(t, payload.Uuid, peer.InvitationId)
}
// TestInvitationFullFlow exercises the complete 4-step invitation handshake end-to-end,
// verifying that both peers end up with each other's full contact information.
func TestInvitationFullFlow(t *testing.T) {
initiator, invitee, cleanup := setupInvitationTest(t)
defer cleanup()
// STEP_1: initiator creates init payload.
payload, initPeer, err := initiator.InvitationStep1("Alice", "Bob", []string{"http://init.server/meow/"}, "Hello!")
assert.NoError(t, err)
assert.NotNil(t, initPeer.InvitationKp)
assert.Nil(t, initPeer.MyIdentity)
// STEP_2: invitee creates their peer from the payload.
srvCard := &meowlib.ServerCard{Name: "InviteeServer", Url: "http://invitee.server/meow/"}
inviteePeer, err := invitee.InvitationStep2("Bob", "Alice", []string{"http://invitee.server/meow/"}, payload)
assert.NoError(t, err)
inviteeCC := inviteePeer.GetMyContact()
inviteeCC.PullServers = append(inviteeCC.PullServers, srvCard)
// STEP_3: initiator receives invitee's CC, generates full keypairs.
myCC, _, err := initiator.InvitationStep3(inviteeCC)
assert.NoError(t, err)
assert.NotNil(t, myCC)
assert.NotEmpty(t, myCC.ContactPublicKey)
assert.NotEmpty(t, myCC.EncryptionPublicKey)
assert.NotEmpty(t, myCC.LookupPublicKey)
assert.NotEmpty(t, myCC.DrRootKey)
assert.NotEmpty(t, myCC.DrPublicKey)
// After step 3, initiator's peer must have full keypairs and invitee's contact info.
updatedInitPeer := initiator.Peers.GetFromInvitationId(payload.Uuid)
assert.NotNil(t, updatedInitPeer.MyIdentity)
assert.NotNil(t, updatedInitPeer.MyEncryptionKp)
assert.NotNil(t, updatedInitPeer.MyLookupKp)
assert.Equal(t, inviteePeer.MyIdentity.Public, updatedInitPeer.ContactPublicKey)
assert.Equal(t, inviteePeer.MyEncryptionKp.Public, updatedInitPeer.ContactEncryption)
assert.Nil(t, updatedInitPeer.InvitationKp) // temp key must be cleared
// STEP_4: invitee finalizes from initiator's full CC.
srvCardInit := &meowlib.ServerCard{Name: "InitServer", Url: "http://init.server/meow/"}
myCC.PullServers = append(myCC.PullServers, srvCardInit)
err = invitee.InvitationStep4(myCC)
assert.NoError(t, err)
// Both peers must now be fully finalized (ContactPublicKey set → not pending).
finalInitPeer := initiator.Peers.GetFromInvitationId(payload.Uuid)
assert.False(t, finalInitPeer.InvitationPending())
finalInviteePeer := invitee.Peers.GetFromInvitationId(payload.Uuid)
assert.False(t, finalInviteePeer.InvitationPending())
assert.Equal(t, updatedInitPeer.MyIdentity.Public, finalInviteePeer.ContactPublicKey)
assert.Equal(t, updatedInitPeer.MyEncryptionKp.Public, finalInviteePeer.ContactEncryption)
assert.Equal(t, updatedInitPeer.MyLookupKp.Public, finalInviteePeer.ContactLookupKey)
assert.NotEmpty(t, finalInviteePeer.DrRootKey)
}
// TestInvitationStep3NotFound verifies that InvitationStep3 returns an error when no
// pending peer exists for the given invitation ID.
func TestInvitationStep3NotFound(t *testing.T) {
initiator, _, cleanup := setupInvitationTest(t)
defer cleanup()
cc := &meowlib.ContactCard{InvitationId: "nonexistent-uuid", ContactPublicKey: "pub"}
_, _, err := initiator.InvitationStep3(cc)
assert.Error(t, err)
}
// TestGetRequestJobsPendingPeer verifies that pending (step-1 only) peers contribute
// their InvitationKp to GetRequestJobs instead of MyLookupKp.
func TestGetRequestJobsPendingPeer(t *testing.T) {
cfg := GetConfig()
cfg.SetMemPass("testpass")
id, err := CreateIdentity("testjobs")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(cfg.StoragePath + "/" + id.Uuid)
cfg.SetIdentity(id)
id.MessageServers = ServerStorage{DbFile: "testjobs.db"}
defer os.RemoveAll("testjobs.db")
srv, _ := CreateServerFromUrl("http://srv1.test/meow/")
id.MessageServers.StoreServer(srv)
// Create a step-1 pending peer.
_, _, err = id.InvitationStep1("Me", "Friend", []string{"http://srv1.test/meow/"}, "Hi")
assert.NoError(t, err)
jobs := id.GetRequestJobs()
// At least one job should have a lookup key (the InvitationKp).
total := 0
for _, j := range jobs {
total += len(j.LookupKeys)
}
assert.Greater(t, total, 0)
}