//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) }