206 lines
7.5 KiB
Go
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)
|
||
|
|
}
|