357 lines
9.4 KiB
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)
|
|
}
|