Files

137 lines
5.1 KiB
Go
Raw Permalink Normal View History

2026-04-12 13:38:15 +02:00
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
}
2026-04-14 19:12:09 +02:00
// 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) {
2026-04-12 13:38:15 +02:00
cfg := client.GetConfig()
cfg.SetMemPass("testpass") //nolint:errcheck
initiator, cleanInit := setupIdentity(t, "alice")
defer cleanInit()
2026-04-14 19:12:09 +02:00
step1Bytes, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "Hello!", nil)
2026-04-12 13:38:15 +02:00
require.NoError(t, err)
2026-04-14 19:12:09 +02:00
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")
2026-04-12 13:38:15 +02:00
require.NotNil(t, initPeer)
assert.Nil(t, initPeer.MyIdentity)
assert.NotNil(t, initPeer.InvitationKp)
}
2026-04-14 19:12:09 +02:00
// 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) {
2026-04-12 13:38:15 +02:00
cfg := client.GetConfig()
cfg.SetMemPass("testpass") //nolint:errcheck
2026-04-14 19:12:09 +02:00
// --- STEP 1: initiator creates temp keypair, gets binary payload ---
2026-04-12 13:38:15 +02:00
initiator, cleanInit := setupIdentity(t, "alice2")
defer cleanInit()
2026-04-14 19:12:09 +02:00
step1Bytes, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "", nil)
2026-04-12 13:38:15 +02:00
require.NoError(t, err)
2026-04-14 19:12:09 +02:00
require.NotEmpty(t, step1Bytes)
2026-04-12 13:38:15 +02:00
2026-04-14 19:12:09 +02:00
// --- STEP 2: invitee creates peer, returns serialized Invitation (step=2) ---
invitee, cleanInvitee := setupIdentity(t, "bob2")
2026-04-12 13:38:15 +02:00
defer cleanInvitee()
2026-04-14 19:12:09 +02:00
step2Bytes, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(step1Bytes, "Alice", "Bob", nil)
2026-04-12 13:38:15 +02:00
require.NoError(t, err)
2026-04-14 19:12:09 +02:00
require.NotEmpty(t, step2Bytes, "step2 must return non-empty invitation bytes")
2026-04-12 13:38:15 +02:00
2026-04-14 19:12:09 +02:00
// 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)
2026-04-12 13:38:15 +02:00
2026-04-14 19:12:09 +02:00
// 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)
2026-04-12 13:38:15 +02:00
2026-04-14 19:12:09 +02:00
// --- STEP 3: initiator decrypts invitee's card, returns serialized Invitation (step=3) ---
cfg.SetIdentity(initiator)
2026-04-12 13:38:15 +02:00
2026-04-14 19:12:09 +02:00
step3Bytes, err := messages.Step3InitiatorFinalizesInviteeAndCreatesContactCard(step2Bytes)
2026-04-12 13:38:15 +02:00
require.NoError(t, err)
2026-04-14 19:12:09 +02:00
require.NotEmpty(t, step3Bytes)
2026-04-12 13:38:15 +02:00
2026-04-14 19:12:09 +02:00
// 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)
2026-04-12 13:38:15 +02:00
}