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 } // TestStep2ProducesPackedUserMessage verifies that Step2 returns a PackedUserMessage // (not just a peer) and that the message is encrypted with the initiator's temp key // so Step3 can decrypt it. func TestStep2ProducesPackedUserMessage(t *testing.T) { cfg := client.GetConfig() cfg.SetMemPass("testpass") //nolint:errcheck // --- STEP 1: initiator creates temp keypair and payload --- initiator, cleanInit := setupIdentity(t, "alice") defer cleanInit() payload, initPeer, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "Hello!", nil) require.NoError(t, err) require.NotNil(t, payload) require.NotNil(t, initPeer) // Initiator has only the temp keypair at this stage. assert.Nil(t, initPeer.MyIdentity) assert.NotNil(t, initPeer.InvitationKp) // --- STEP 2: invitee receives payload, creates peer, returns packed message --- _, cleanInvitee := setupIdentity(t, "bob") defer cleanInvitee() packed, inviteePeer, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard( payload, "Alice", "Bob", nil, ) require.NoError(t, err) require.NotNil(t, packed, "step2 must return a PackedUserMessage, not just a peer") require.NotNil(t, inviteePeer) // The packed message destination is the initiator's temp key (used as lookup key). assert.Equal(t, payload.PublicKey, packed.Destination) // The invitee peer has full keypairs now. assert.NotNil(t, inviteePeer.MyIdentity) assert.NotNil(t, inviteePeer.MyEncryptionKp) assert.NotNil(t, inviteePeer.MyLookupKp) // --- STEP 3: initiator decrypts invitee's packed message and finalises --- cfg.SetIdentity(initiator) // Simulate how the server delivers the step-2 answer: marshal the PackedUserMessage // into an Invitation.Payload. packedBytes, err := proto.Marshal(packed) require.NoError(t, err) invitation := &meowlib.Invitation{ Uuid: payload.Uuid, Step: 2, From: inviteePeer.MyIdentity.Public, Payload: packedBytes, } peer, myCC, err := messages.Step3InitiatorFinalizesInviteeAndCreatesContactCard(invitation) require.NoError(t, err) require.NotNil(t, peer) require.NotNil(t, myCC, "step3 must produce the initiator's ContactCard to send to invitee") // Initiator's peer must now hold invitee's real keys. assert.Equal(t, inviteePeer.MyIdentity.Public, peer.ContactPublicKey) assert.Equal(t, inviteePeer.MyEncryptionKp.Public, peer.ContactEncryption) assert.Equal(t, inviteePeer.MyLookupKp.Public, peer.ContactLookupKey) assert.Nil(t, peer.InvitationKp, "temp keypair must be cleared after step3") assert.NotEmpty(t, myCC.DrRootKey) assert.NotEmpty(t, myCC.DrPublicKey) } // TestStep2Step3RoundTripPayload verifies that the PackedUserMessage produced by step2 // actually carries the invitee's ContactCard when decrypted by the initiator. func TestStep2Step3RoundTripPayload(t *testing.T) { cfg := client.GetConfig() cfg.SetMemPass("testpass") //nolint:errcheck initiator, cleanInit := setupIdentity(t, "alice2") defer cleanInit() payload, _, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "", nil) require.NoError(t, err) _, cleanInvitee := setupIdentity(t, "bob2") defer cleanInvitee() packed, inviteePeer, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(payload, "Alice", "Bob", nil) require.NoError(t, err) // Confirm the message serialises cleanly (transport simulation). packedBytes, err := proto.Marshal(packed) require.NoError(t, err) assert.NotEmpty(t, packedBytes) // Switch back to initiator and run step3. cfg.SetIdentity(initiator) var roundTripped meowlib.PackedUserMessage require.NoError(t, proto.Unmarshal(packedBytes, &roundTripped)) invitation := &meowlib.Invitation{ Uuid: payload.Uuid, Step: 2, From: inviteePeer.MyIdentity.Public, Payload: packedBytes, } _, myCC, err := messages.Step3InitiatorFinalizesInviteeAndCreatesContactCard(invitation) require.NoError(t, err) require.NotNil(t, myCC) // The initiator's CC must reference the invitee's invitation ID so the invitee // can match it when step4 arrives. assert.Equal(t, payload.Uuid, myCC.InvitationId) }