This commit is contained in:
205
client/invitation_test.go
Normal file
205
client/invitation_test.go
Normal file
@@ -0,0 +1,205 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user