137 lines
5.1 KiB
Go
137 lines
5.1 KiB
Go
package messages_test
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
|
|
"forge.redroom.link/yves/meowlib"
|
|
"forge.redroom.link/yves/meowlib/client"
|
|
"forge.redroom.link/yves/meowlib/client/invitation/messages"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
// setupIdentity creates a fresh identity and sets it as the active config identity.
|
|
// Returns the identity and a cleanup function.
|
|
func setupIdentity(t *testing.T, nickname string) (*client.Identity, func()) {
|
|
t.Helper()
|
|
cfg := client.GetConfig()
|
|
cfg.SetMemPass("testpass") //nolint:errcheck
|
|
|
|
id, err := client.CreateIdentity(nickname)
|
|
require.NoError(t, err)
|
|
cfg.SetIdentity(id)
|
|
|
|
cleanup := func() {
|
|
os.RemoveAll(cfg.StoragePath + "/" + id.Uuid)
|
|
}
|
|
return id, cleanup
|
|
}
|
|
|
|
// TestStep1ReturnsBinaryPayload verifies that Step1 returns non-empty bytes that
|
|
// deserialise to a valid InvitationInitPayload, and that the pending peer is stored
|
|
// with only a temp keypair (no real identity keys yet).
|
|
func TestStep1ReturnsBinaryPayload(t *testing.T) {
|
|
cfg := client.GetConfig()
|
|
cfg.SetMemPass("testpass") //nolint:errcheck
|
|
|
|
initiator, cleanInit := setupIdentity(t, "alice")
|
|
defer cleanInit()
|
|
|
|
step1Bytes, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "Hello!", nil)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, step1Bytes)
|
|
|
|
var payload meowlib.InvitationInitPayload
|
|
require.NoError(t, proto.Unmarshal(step1Bytes, &payload))
|
|
assert.NotEmpty(t, payload.Uuid)
|
|
assert.NotEmpty(t, payload.PublicKey)
|
|
assert.Equal(t, "Alice", payload.Name)
|
|
assert.Equal(t, "Hello!", payload.InvitationMessage)
|
|
|
|
// Peer is saved with temp keypair only — no real identity keys yet.
|
|
initPeer := initiator.Peers.GetFromName("Bob")
|
|
require.NotNil(t, initPeer)
|
|
assert.Nil(t, initPeer.MyIdentity)
|
|
assert.NotNil(t, initPeer.InvitationKp)
|
|
}
|
|
|
|
// TestFullInvitationFlow runs all four steps end-to-end, passing the binary output of
|
|
// each step directly to the next, and verifies that both peers end up with each other's
|
|
// real keys after the exchange completes.
|
|
func TestFullInvitationFlow(t *testing.T) {
|
|
cfg := client.GetConfig()
|
|
cfg.SetMemPass("testpass") //nolint:errcheck
|
|
|
|
// --- STEP 1: initiator creates temp keypair, gets binary payload ---
|
|
initiator, cleanInit := setupIdentity(t, "alice2")
|
|
defer cleanInit()
|
|
|
|
step1Bytes, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "", nil)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, step1Bytes)
|
|
|
|
// --- STEP 2: invitee creates peer, returns serialized Invitation (step=2) ---
|
|
invitee, cleanInvitee := setupIdentity(t, "bob2")
|
|
defer cleanInvitee()
|
|
|
|
step2Bytes, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(step1Bytes, "Alice", "Bob", nil)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, step2Bytes, "step2 must return non-empty invitation bytes")
|
|
|
|
// Invitee now has a peer with full keypairs.
|
|
inviteePeer := invitee.Peers.GetFromName("Alice")
|
|
require.NotNil(t, inviteePeer)
|
|
assert.NotNil(t, inviteePeer.MyIdentity)
|
|
assert.NotNil(t, inviteePeer.MyEncryptionKp)
|
|
assert.NotNil(t, inviteePeer.MyLookupKp)
|
|
|
|
// The step-2 wire format is a serialized Invitation.
|
|
var inv2 meowlib.Invitation
|
|
require.NoError(t, proto.Unmarshal(step2Bytes, &inv2))
|
|
assert.EqualValues(t, 2, inv2.Step)
|
|
assert.NotEmpty(t, inv2.Uuid)
|
|
assert.Equal(t, inviteePeer.MyIdentity.Public, inv2.From)
|
|
assert.NotEmpty(t, inv2.Payload)
|
|
|
|
// --- STEP 3: initiator decrypts invitee's card, returns serialized Invitation (step=3) ---
|
|
cfg.SetIdentity(initiator)
|
|
|
|
step3Bytes, err := messages.Step3InitiatorFinalizesInviteeAndCreatesContactCard(step2Bytes)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, step3Bytes)
|
|
|
|
// Initiator's peer must now hold invitee's real keys; temp keypair must be gone.
|
|
initPeer := initiator.Peers.GetFromName("Bob")
|
|
require.NotNil(t, initPeer)
|
|
assert.Equal(t, inviteePeer.MyIdentity.Public, initPeer.ContactPublicKey)
|
|
assert.Equal(t, inviteePeer.MyEncryptionKp.Public, initPeer.ContactEncryption)
|
|
assert.Equal(t, inviteePeer.MyLookupKp.Public, initPeer.ContactLookupKey)
|
|
assert.Nil(t, initPeer.InvitationKp, "temp keypair must be cleared after step3")
|
|
assert.NotEmpty(t, initPeer.DrKpPublic)
|
|
assert.NotEmpty(t, initPeer.DrRootKey)
|
|
|
|
// The step-3 wire format is a serialized Invitation.
|
|
var inv3 meowlib.Invitation
|
|
require.NoError(t, proto.Unmarshal(step3Bytes, &inv3))
|
|
assert.EqualValues(t, 3, inv3.Step)
|
|
assert.NotEmpty(t, inv3.Uuid)
|
|
assert.NotEmpty(t, inv3.Payload)
|
|
|
|
// --- STEP 4: invitee finalises initiator ---
|
|
cfg.SetIdentity(invitee)
|
|
|
|
finalPeer, err := messages.Step4InviteeFinalizesInitiator(step3Bytes)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, finalPeer)
|
|
|
|
// Invitee's peer must now hold initiator's real keys and the invitation must be complete.
|
|
assert.Equal(t, initPeer.MyIdentity.Public, finalPeer.ContactPublicKey)
|
|
assert.Equal(t, initPeer.MyEncryptionKp.Public, finalPeer.ContactEncryption)
|
|
assert.Equal(t, initPeer.MyLookupKp.Public, finalPeer.ContactLookupKey)
|
|
assert.False(t, finalPeer.InvitationPending(), "invitation must be fully finalized")
|
|
assert.NotEmpty(t, finalPeer.DrRootKey)
|
|
assert.NotEmpty(t, finalPeer.ContactDrPublicKey)
|
|
}
|