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