rwadurian/backend/mpc-system/tests/e2e/keygen_flow_test.go

357 lines
9.4 KiB
Go

//go:build e2e
package e2e_test
import (
"bytes"
"encoding/json"
"net/http"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type KeygenFlowTestSuite struct {
suite.Suite
baseURL string
client *http.Client
}
func TestKeygenFlowSuite(t *testing.T) {
if testing.Short() {
t.Skip("Skipping e2e test in short mode")
}
suite.Run(t, new(KeygenFlowTestSuite))
}
func (s *KeygenFlowTestSuite) SetupSuite() {
s.baseURL = os.Getenv("SESSION_COORDINATOR_URL")
if s.baseURL == "" {
s.baseURL = "http://localhost:8080"
}
s.client = &http.Client{
Timeout: 30 * time.Second,
}
// Wait for service to be ready
s.waitForService()
}
func (s *KeygenFlowTestSuite) waitForService() {
maxRetries := 30
for i := 0; i < maxRetries; i++ {
resp, err := s.client.Get(s.baseURL + "/health")
if err == nil && resp.StatusCode == http.StatusOK {
resp.Body.Close()
return
}
if resp != nil {
resp.Body.Close()
}
time.Sleep(time.Second)
}
s.T().Fatal("Service not ready after waiting")
}
type CreateSessionRequest struct {
SessionType string `json:"sessionType"`
ThresholdT int `json:"thresholdT"`
ThresholdN int `json:"thresholdN"`
CreatedBy string `json:"createdBy"`
}
type CreateSessionResponse struct {
SessionID string `json:"sessionId"`
JoinToken string `json:"joinToken"`
Status string `json:"status"`
}
type JoinSessionRequest struct {
JoinToken string `json:"joinToken"`
PartyID string `json:"partyId"`
DeviceType string `json:"deviceType"`
DeviceID string `json:"deviceId"`
}
type JoinSessionResponse struct {
SessionID string `json:"sessionId"`
PartyIndex int `json:"partyIndex"`
Status string `json:"status"`
Participants []struct {
PartyID string `json:"partyId"`
Status string `json:"status"`
} `json:"participants"`
}
type SessionStatusResponse struct {
SessionID string `json:"sessionId"`
Status string `json:"status"`
ThresholdT int `json:"thresholdT"`
ThresholdN int `json:"thresholdN"`
Participants []struct {
PartyID string `json:"partyId"`
PartyIndex int `json:"partyIndex"`
Status string `json:"status"`
} `json:"participants"`
}
func (s *KeygenFlowTestSuite) TestCompleteKeygenFlow() {
// Step 1: Create a new keygen session
createReq := CreateSessionRequest{
SessionType: "keygen",
ThresholdT: 2,
ThresholdN: 3,
CreatedBy: "e2e_test_user",
}
createResp := s.createSession(createReq)
require.NotEmpty(s.T(), createResp.SessionID)
require.NotEmpty(s.T(), createResp.JoinToken)
assert.Equal(s.T(), "created", createResp.Status)
sessionID := createResp.SessionID
joinToken := createResp.JoinToken
// Step 2: First party joins
joinReq1 := JoinSessionRequest{
JoinToken: joinToken,
PartyID: "party_user_device",
DeviceType: "iOS",
DeviceID: "device_001",
}
joinResp1 := s.joinSession(joinReq1)
assert.Equal(s.T(), sessionID, joinResp1.SessionID)
assert.Equal(s.T(), 0, joinResp1.PartyIndex)
// Step 3: Second party joins
joinReq2 := JoinSessionRequest{
JoinToken: joinToken,
PartyID: "party_server",
DeviceType: "server",
DeviceID: "server_001",
}
joinResp2 := s.joinSession(joinReq2)
assert.Equal(s.T(), sessionID, joinResp2.SessionID)
assert.Equal(s.T(), 1, joinResp2.PartyIndex)
// Step 4: Third party joins
joinReq3 := JoinSessionRequest{
JoinToken: joinToken,
PartyID: "party_recovery",
DeviceType: "recovery",
DeviceID: "recovery_001",
}
joinResp3 := s.joinSession(joinReq3)
assert.Equal(s.T(), sessionID, joinResp3.SessionID)
assert.Equal(s.T(), 2, joinResp3.PartyIndex)
// Step 5: Check session status - should have all participants
statusResp := s.getSessionStatus(sessionID)
assert.Equal(s.T(), 3, len(statusResp.Participants))
// Step 6: Mark parties as ready
s.markPartyReady(sessionID, "party_user_device")
s.markPartyReady(sessionID, "party_server")
s.markPartyReady(sessionID, "party_recovery")
// Step 7: Start the session
s.startSession(sessionID)
// Step 8: Verify session is in progress
statusResp = s.getSessionStatus(sessionID)
assert.Equal(s.T(), "in_progress", statusResp.Status)
// Step 9: Report completion for all participants (simulate keygen completion)
publicKey := []byte("test-group-public-key-from-keygen")
s.reportCompletion(sessionID, "party_user_device", publicKey)
s.reportCompletion(sessionID, "party_server", publicKey)
s.reportCompletion(sessionID, "party_recovery", publicKey)
// Step 10: Verify session is completed
statusResp = s.getSessionStatus(sessionID)
assert.Equal(s.T(), "completed", statusResp.Status)
}
func (s *KeygenFlowTestSuite) TestJoinSessionWithInvalidToken() {
joinReq := JoinSessionRequest{
JoinToken: "invalid-token",
PartyID: "party_test",
DeviceType: "iOS",
DeviceID: "device_test",
}
body, _ := json.Marshal(joinReq)
resp, err := s.client.Post(
s.baseURL+"/api/v1/sessions/join",
"application/json",
bytes.NewReader(body),
)
require.NoError(s.T(), err)
defer resp.Body.Close()
assert.Equal(s.T(), http.StatusUnauthorized, resp.StatusCode)
}
func (s *KeygenFlowTestSuite) TestGetNonExistentSession() {
resp, err := s.client.Get(s.baseURL + "/api/v1/sessions/00000000-0000-0000-0000-000000000000")
require.NoError(s.T(), err)
defer resp.Body.Close()
assert.Equal(s.T(), http.StatusNotFound, resp.StatusCode)
}
func (s *KeygenFlowTestSuite) TestExceedParticipantLimit() {
// Create session with 2 participants max
createReq := CreateSessionRequest{
SessionType: "keygen",
ThresholdT: 2,
ThresholdN: 2,
CreatedBy: "e2e_test_user_limit",
}
createResp := s.createSession(createReq)
joinToken := createResp.JoinToken
// Join with 2 parties (should succeed)
for i := 0; i < 2; i++ {
joinReq := JoinSessionRequest{
JoinToken: joinToken,
PartyID: "party_" + string(rune('a'+i)),
DeviceType: "test",
DeviceID: "device_" + string(rune('a'+i)),
}
s.joinSession(joinReq)
}
// Try to join with 3rd party (should fail)
joinReq := JoinSessionRequest{
JoinToken: joinToken,
PartyID: "party_extra",
DeviceType: "test",
DeviceID: "device_extra",
}
body, _ := json.Marshal(joinReq)
resp, err := s.client.Post(
s.baseURL+"/api/v1/sessions/join",
"application/json",
bytes.NewReader(body),
)
require.NoError(s.T(), err)
defer resp.Body.Close()
assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode)
}
// Helper methods
func (s *KeygenFlowTestSuite) createSession(req CreateSessionRequest) CreateSessionResponse {
body, _ := json.Marshal(req)
resp, err := s.client.Post(
s.baseURL+"/api/v1/sessions",
"application/json",
bytes.NewReader(body),
)
require.NoError(s.T(), err)
defer resp.Body.Close()
require.Equal(s.T(), http.StatusCreated, resp.StatusCode)
var result CreateSessionResponse
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(s.T(), err)
return result
}
func (s *KeygenFlowTestSuite) joinSession(req JoinSessionRequest) JoinSessionResponse {
body, _ := json.Marshal(req)
resp, err := s.client.Post(
s.baseURL+"/api/v1/sessions/join",
"application/json",
bytes.NewReader(body),
)
require.NoError(s.T(), err)
defer resp.Body.Close()
require.Equal(s.T(), http.StatusOK, resp.StatusCode)
var result JoinSessionResponse
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(s.T(), err)
return result
}
func (s *KeygenFlowTestSuite) getSessionStatus(sessionID string) SessionStatusResponse {
resp, err := s.client.Get(s.baseURL + "/api/v1/sessions/" + sessionID)
require.NoError(s.T(), err)
defer resp.Body.Close()
require.Equal(s.T(), http.StatusOK, resp.StatusCode)
var result SessionStatusResponse
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(s.T(), err)
return result
}
func (s *KeygenFlowTestSuite) markPartyReady(sessionID, partyID string) {
req := map[string]string{"partyId": partyID}
body, _ := json.Marshal(req)
httpReq, _ := http.NewRequest(
http.MethodPut,
s.baseURL+"/api/v1/sessions/"+sessionID+"/parties/"+partyID+"/ready",
bytes.NewReader(body),
)
httpReq.Header.Set("Content-Type", "application/json")
resp, err := s.client.Do(httpReq)
require.NoError(s.T(), err)
defer resp.Body.Close()
require.Equal(s.T(), http.StatusOK, resp.StatusCode)
}
func (s *KeygenFlowTestSuite) startSession(sessionID string) {
httpReq, _ := http.NewRequest(
http.MethodPost,
s.baseURL+"/api/v1/sessions/"+sessionID+"/start",
nil,
)
resp, err := s.client.Do(httpReq)
require.NoError(s.T(), err)
defer resp.Body.Close()
require.Equal(s.T(), http.StatusOK, resp.StatusCode)
}
func (s *KeygenFlowTestSuite) reportCompletion(sessionID string, partyID string, publicKey []byte) {
req := map[string]interface{}{
"party_id": partyID,
"public_key": string(publicKey),
}
body, _ := json.Marshal(req)
httpReq, _ := http.NewRequest(
http.MethodPost,
s.baseURL+"/api/v1/sessions/"+sessionID+"/complete",
bytes.NewReader(body),
)
httpReq.Header.Set("Content-Type", "application/json")
resp, err := s.client.Do(httpReq)
require.NoError(s.T(), err)
defer resp.Body.Close()
require.Equal(s.T(), http.StatusOK, resp.StatusCode)
}