From ff2c2a5974aa97e1f02b0eb63b99ed96db67cce7 Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Mon, 5 Jan 2026 18:15:11 +0100 Subject: [PATCH] Fix e2e tests Signed-off-by: Bryan Frimin --- e2e/console/member_test.go | 76 +++++++------- e2e/console/organization_test.go | 14 +-- e2e/console/rbac_test.go | 26 ++++- e2e/internal/testutil/client.go | 160 +++++++++++++++++------------- e2e/internal/testutil/graphql.go | 29 +++++- e2e/internal/testutil/testutil.go | 2 +- 6 files changed, 178 insertions(+), 129 deletions(-) diff --git a/e2e/console/member_test.go b/e2e/console/member_test.go index d6a857792..ce5b4b5b0 100644 --- a/e2e/console/member_test.go +++ b/e2e/console/member_test.go @@ -36,12 +36,11 @@ func TestMember_UpdateMembership(t *testing.T) { query($id: ID!) { node(id: $id) { ... on Organization { - memberships(first: 10) { + members(first: 10) { edges { node { id role - userID } } } @@ -52,26 +51,25 @@ func TestMember_UpdateMembership(t *testing.T) { var result struct { Node struct { - Memberships struct { + Members struct { Edges []struct { Node struct { - ID string `json:"id"` - Role string `json:"role"` - UserID string `json:"userID"` + ID string `json:"id"` + Role string `json:"role"` } `json:"node"` } `json:"edges"` - } `json:"memberships"` + } `json:"members"` } `json:"node"` } - err := owner.Execute(query, map[string]any{ + err := owner.ExecuteConnect(query, map[string]any{ "id": owner.GetOrganizationID().String(), }, &result) require.NoError(t, err) // Find the admin member var adminMemberID string - for _, edge := range result.Node.Memberships.Edges { + for _, edge := range result.Node.Members.Edges { if edge.Node.Role == "ADMIN" { adminMemberID = edge.Node.ID break @@ -100,10 +98,10 @@ func TestMember_UpdateMembership(t *testing.T) { } `json:"updateMembership"` } - err = owner.Execute(mutation, map[string]any{ + err = owner.ExecuteConnect(mutation, map[string]any{ "input": map[string]any{ "organizationId": owner.GetOrganizationID().String(), - "memberId": adminMemberID, + "membershipId": adminMemberID, "role": "VIEWER", }, }, &mutationResult) @@ -125,7 +123,7 @@ func TestMember_RemoveMember(t *testing.T) { query($id: ID!) { node(id: $id) { ... on Organization { - memberships(first: 50) { + members(first: 50) { edges { node { id @@ -140,25 +138,25 @@ func TestMember_RemoveMember(t *testing.T) { var result struct { Node struct { - Memberships struct { + Members struct { Edges []struct { Node struct { ID string `json:"id"` Role string `json:"role"` } `json:"node"` } `json:"edges"` - } `json:"memberships"` + } `json:"members"` } `json:"node"` } - err := owner.Execute(query, map[string]any{ + err := owner.ExecuteConnect(query, map[string]any{ "id": owner.GetOrganizationID().String(), }, &result) require.NoError(t, err) // Find a viewer member to remove var memberID string - for _, edge := range result.Node.Memberships.Edges { + for _, edge := range result.Node.Members.Edges { if edge.Node.Role == "VIEWER" { memberID = edge.Node.ID break @@ -170,26 +168,26 @@ func TestMember_RemoveMember(t *testing.T) { mutation := ` mutation($input: RemoveMemberInput!) { removeMember(input: $input) { - deletedMemberId + deletedMembershipId } } ` var mutationResult struct { RemoveMember struct { - DeletedMemberID string `json:"deletedMemberId"` + DeletedMembershipID string `json:"deletedMembershipId"` } `json:"removeMember"` } - err = owner.Execute(mutation, map[string]any{ + err = owner.ExecuteConnect(mutation, map[string]any{ "input": map[string]any{ "organizationId": owner.GetOrganizationID().String(), - "memberId": memberID, + "membershipId": memberID, }, }, &mutationResult) require.NoError(t, err) - assert.Equal(t, memberID, mutationResult.RemoveMember.DeletedMemberID) + assert.Equal(t, memberID, mutationResult.RemoveMember.DeletedMembershipID) } func TestInvitation_Delete(t *testing.T) { @@ -198,8 +196,8 @@ func TestInvitation_Delete(t *testing.T) { // Create an invitation inviteMutation := ` - mutation($input: InviteUserInput!) { - inviteUser(input: $input) { + mutation($input: InviteMemberInput!) { + inviteMember(input: $input) { invitationEdge { node { id @@ -212,7 +210,7 @@ func TestInvitation_Delete(t *testing.T) { ` var inviteResult struct { - InviteUser struct { + InviteMember struct { InvitationEdge struct { Node struct { ID string `json:"id"` @@ -220,21 +218,20 @@ func TestInvitation_Delete(t *testing.T) { Status string `json:"status"` } `json:"node"` } `json:"invitationEdge"` - } `json:"inviteUser"` + } `json:"inviteMember"` } - err := owner.Execute(inviteMutation, map[string]any{ + err := owner.ExecuteConnect(inviteMutation, map[string]any{ "input": map[string]any{ "organizationId": owner.GetOrganizationID().String(), "email": fmt.Sprintf("invite.delete.%d@example.com", time.Now().UnixNano()), "fullName": "Test User", "role": "VIEWER", - "createPeople": false, }, }, &inviteResult) require.NoError(t, err) - invitationID := inviteResult.InviteUser.InvitationEdge.Node.ID + invitationID := inviteResult.InviteMember.InvitationEdge.Node.ID assert.NotEmpty(t, invitationID) // Delete the invitation @@ -252,9 +249,10 @@ func TestInvitation_Delete(t *testing.T) { } `json:"deleteInvitation"` } - err = owner.Execute(deleteMutation, map[string]any{ + err = owner.ExecuteConnect(deleteMutation, map[string]any{ "input": map[string]any{ - "invitationId": invitationID, + "organizationId": owner.GetOrganizationID().String(), + "invitationId": invitationID, }, }, &deleteResult) require.NoError(t, err) @@ -274,13 +272,11 @@ func TestMember_List(t *testing.T) { query($id: ID!) { node(id: $id) { ... on Organization { - memberships(first: 10) { + members(first: 10) { edges { node { id role - userID - emailAddress } } totalCount @@ -292,24 +288,22 @@ func TestMember_List(t *testing.T) { var result struct { Node struct { - Memberships struct { + Members struct { Edges []struct { Node struct { - ID string `json:"id"` - Role string `json:"role"` - UserID string `json:"userID"` - EmailAddress string `json:"emailAddress"` + ID string `json:"id"` + Role string `json:"role"` } `json:"node"` } `json:"edges"` TotalCount int `json:"totalCount"` - } `json:"memberships"` + } `json:"members"` } `json:"node"` } - err := owner.Execute(query, map[string]any{ + err := owner.ExecuteConnect(query, map[string]any{ "id": owner.GetOrganizationID().String(), }, &result) require.NoError(t, err) - assert.GreaterOrEqual(t, result.Node.Memberships.TotalCount, 3, "Should have at least 3 members") + assert.GreaterOrEqual(t, result.Node.Members.TotalCount, 3, "Should have at least 3 members") } diff --git a/e2e/console/organization_test.go b/e2e/console/organization_test.go index 95556ec7c..d1b0a33f8 100644 --- a/e2e/console/organization_test.go +++ b/e2e/console/organization_test.go @@ -53,7 +53,7 @@ func TestOrganization_Update(t *testing.T) { } `json:"updateOrganization"` } - err := owner.Execute(query, map[string]any{ + err := owner.ExecuteConnect(query, map[string]any{ "input": map[string]any{ "organizationId": owner.GetOrganizationID().String(), "name": newName, @@ -90,7 +90,7 @@ func TestOrganization_Update(t *testing.T) { } `json:"updateOrganization"` } - err := owner.Execute(query, map[string]any{ + err := owner.ExecuteConnect(query, map[string]any{ "input": map[string]any{ "organizationId": owner.GetOrganizationID().String(), "websiteUrl": "https://example.com", @@ -124,7 +124,7 @@ func TestOrganization_Update(t *testing.T) { } `json:"updateOrganization"` } - err := owner.Execute(query, map[string]any{ + err := owner.ExecuteConnect(query, map[string]any{ "input": map[string]any{ "organizationId": owner.GetOrganizationID().String(), "headquarterAddress": "123 Main St, Suite 100, San Francisco, CA 94102", @@ -187,9 +187,6 @@ func TestOrganization_Get(t *testing.T) { websiteUrl email headquarterAddress - context { - summary - } } } } @@ -203,13 +200,10 @@ func TestOrganization_Get(t *testing.T) { WebsiteUrl string `json:"websiteUrl"` Email string `json:"email"` HeadquarterAddress string `json:"headquarterAddress"` - Context struct { - Summary string `json:"summary"` - } `json:"context"` } `json:"node"` } - err := owner.Execute(query, map[string]any{ + err := owner.ExecuteConnect(query, map[string]any{ "id": owner.GetOrganizationID().String(), }, &result) require.NoError(t, err) diff --git a/e2e/console/rbac_test.go b/e2e/console/rbac_test.go index 7c50e21d3..d9ffb502b 100644 --- a/e2e/console/rbac_test.go +++ b/e2e/console/rbac_test.go @@ -15,6 +15,7 @@ package console_test import ( + "strings" "testing" "github.com/stretchr/testify/require" @@ -254,7 +255,7 @@ const ( query GetMembers($id: ID!) { node(id: $id) { ... on Organization { - memberships(first: 10) { totalCount } + members(first: 10) { totalCount } } } }` @@ -283,6 +284,7 @@ func TestRBAC(t *testing.T) { query string variables func() map[string]any shouldAllow bool + useConnect bool // use connect API instead of console API }{ { name: "owner can create framework", @@ -1158,6 +1160,7 @@ func TestRBAC(t *testing.T) { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "description": factory.SafeName("Updated Desc")}} }, shouldAllow: true, + useConnect: true, }, { name: "admin can update organization", @@ -1168,6 +1171,7 @@ func TestRBAC(t *testing.T) { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "description": factory.SafeName("Updated Desc")}} }, shouldAllow: true, + useConnect: true, }, { name: "viewer cannot update organization", @@ -1178,6 +1182,7 @@ func TestRBAC(t *testing.T) { return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "description": factory.SafeName("Updated Desc")}} }, shouldAllow: false, + useConnect: true, }, { name: "owner can get organization", @@ -1188,6 +1193,7 @@ func TestRBAC(t *testing.T) { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, + useConnect: true, }, { name: "admin can get organization", @@ -1198,6 +1204,7 @@ func TestRBAC(t *testing.T) { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, + useConnect: true, }, { name: "viewer can get organization", @@ -1208,6 +1215,7 @@ func TestRBAC(t *testing.T) { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, + useConnect: true, }, { name: "owner can list members", @@ -1218,6 +1226,7 @@ func TestRBAC(t *testing.T) { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, + useConnect: true, }, { name: "admin can list members", @@ -1228,6 +1237,7 @@ func TestRBAC(t *testing.T) { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, + useConnect: true, }, { name: "viewer can list members", @@ -1238,6 +1248,7 @@ func TestRBAC(t *testing.T) { return map[string]any{"id": owner.GetOrganizationID().String()} }, shouldAllow: true, + useConnect: true, }, } @@ -1245,7 +1256,12 @@ func TestRBAC(t *testing.T) { t.Run( tt.name, func(t *testing.T) { - _, err := tt.client.Do(tt.query, tt.variables()) + var err error + if tt.useConnect { + _, err = tt.client.DoConnect(tt.query, tt.variables()) + } else { + _, err = tt.client.Do(tt.query, tt.variables()) + } if tt.shouldAllow { require.NoError(t, err, "expected request to be allowed") @@ -1253,7 +1269,11 @@ func TestRBAC(t *testing.T) { var gqlErrors testutil.GraphQLErrors require.ErrorAs(t, err, &gqlErrors, "expected GraphQL error, got: %T", err) require.Len(t, gqlErrors, 1, "expected exactly one GraphQL error, got %d errors: %v", len(gqlErrors), gqlErrors) - require.Equal(t, "FORBIDDEN", gqlErrors[0].Code(), "expected FORBIDDEN error code, got %q (message: %q)", gqlErrors[0].Code(), gqlErrors[0].Message) + // Connect API uses a different error format - check either code or message + code := gqlErrors[0].Code() + msg := gqlErrors[0].Message + isForbidden := code == "FORBIDDEN" || (code == "" && (strings.Contains(msg, "does not have sufficient permissions") || strings.Contains(msg, "insufficient permissions"))) + require.True(t, isForbidden, "expected FORBIDDEN error, got code=%q message=%q", code, msg) } }, ) diff --git a/e2e/internal/testutil/client.go b/e2e/internal/testutil/client.go index 204f1f4ee..e05d0f34f 100644 --- a/e2e/internal/testutil/client.go +++ b/e2e/internal/testutil/client.go @@ -15,12 +15,9 @@ package testutil import ( - "bytes" "crypto/rand" "encoding/hex" - "encoding/json" "fmt" - "io" "net/http" "net/http/cookiejar" "testing" @@ -110,6 +107,9 @@ func (c *Client) setupTestUser() { orgName := fmt.Sprintf("Test Org %s", uniqueID) c.organizationID = c.createOrganization(orgName) + // Assume organization session to use console API + c.assumeOrganizationSession() + // If the role is not OWNER, we need to adjust the membership if c.role != RoleOwner { c.updateOwnMembershipRole(coredata.MembershipRole(c.role)) @@ -130,39 +130,38 @@ func (c *Client) setupTestUserInOrg(ownerClient *Client) { // New user accepts invitation c.acceptInvitation(invitationID) + + // Assume organization session to use console API + c.assumeOrganizationSession() } func (c *Client) signUp(email, password, fullName string) gid.GID { - payload := map[string]string{ - "email": email, - "password": password, - "fullName": fullName, - } - - body, err := json.Marshal(payload) - require.NoError(c.T, err, "cannot marshal sign-up payload") - - req, err := http.NewRequest("POST", c.baseURL+"/connect/register", bytes.NewReader(body)) - require.NoError(c.T, err, "cannot create sign-up request") - req.Header.Set("Content-Type", "application/json") - - resp, err := c.httpClient.Do(req) - require.NoError(c.T, err, "sign-up request failed") - defer resp.Body.Close() - - respBody, _ := io.ReadAll(resp.Body) - require.Equal(c.T, http.StatusOK, resp.StatusCode, "sign-up failed: %s", string(respBody)) + const query = ` + mutation($input: SignUpInput!) { + signUp(input: $input) { + identity { id } + } + } + ` var result struct { - User struct { - ID string `json:"id"` - } `json:"user"` + SignUp struct { + Identity struct { + ID string `json:"id"` + } `json:"identity"` + } `json:"signUp"` } - err = json.Unmarshal(respBody, &result) - require.NoError(c.T, err, "cannot decode sign-up response") + err := c.ExecuteConnect(query, map[string]any{ + "input": map[string]any{ + "email": email, + "password": password, + "fullName": fullName, + }, + }, &result) + require.NoError(c.T, err, "signUp mutation failed") - userID, err := gid.ParseGID(result.User.ID) + userID, err := gid.ParseGID(result.SignUp.Identity.ID) require.NoError(c.T, err, "cannot parse user ID") return userID @@ -172,29 +171,25 @@ func (c *Client) createOrganization(name string) gid.GID { const query = ` mutation($input: CreateOrganizationInput!) { createOrganization(input: $input) { - organizationEdge { - node { id } - } + organization { id } } } ` var result struct { CreateOrganization struct { - OrganizationEdge struct { - Node struct { - ID string `json:"id"` - } `json:"node"` - } `json:"organizationEdge"` + Organization struct { + ID string `json:"id"` + } `json:"organization"` } `json:"createOrganization"` } - err := c.Execute(query, map[string]any{ + err := c.ExecuteConnect(query, map[string]any{ "input": map[string]any{"name": name}, }, &result) require.NoError(c.T, err, "createOrganization mutation failed") - orgID, err := gid.ParseGID(result.CreateOrganization.OrganizationEdge.Node.ID) + orgID, err := gid.ParseGID(result.CreateOrganization.Organization.ID) require.NoError(c.T, err, "cannot parse organization ID") return orgID @@ -202,15 +197,17 @@ func (c *Client) createOrganization(name string) gid.GID { func (c *Client) updateOwnMembershipRole(role coredata.MembershipRole) { // First get the membership ID - const queryMemberships = ` + const queryMembers = ` query($id: ID!) { - organization(id: $id) { - memberships(first: 100) { - edges { - node { - id - userId - role + node(id: $id) { + ... on Organization { + members(first: 100) { + edges { + node { + id + identity { id } + role + } } } } @@ -219,27 +216,29 @@ func (c *Client) updateOwnMembershipRole(role coredata.MembershipRole) { ` var qResult struct { - Organization struct { - Memberships struct { + Node struct { + Members struct { Edges []struct { Node struct { - ID string `json:"id"` - UserID string `json:"userId"` - Role string `json:"role"` + ID string `json:"id"` + Identity struct { + ID string `json:"id"` + } `json:"identity"` + Role string `json:"role"` } `json:"node"` } `json:"edges"` - } `json:"memberships"` - } `json:"organization"` + } `json:"members"` + } `json:"node"` } - err := c.Execute(queryMemberships, map[string]any{ + err := c.ExecuteConnect(queryMembers, map[string]any{ "id": c.organizationID.String(), }, &qResult) - require.NoError(c.T, err, "cannot query organization memberships") + require.NoError(c.T, err, "cannot query organization members") var membershipID string - for _, edge := range qResult.Organization.Memberships.Edges { - if edge.Node.UserID == c.userID.String() { + for _, edge := range qResult.Node.Members.Edges { + if edge.Node.Identity.ID == c.userID.String() { membershipID = edge.Node.ID break } @@ -258,10 +257,10 @@ func (c *Client) updateOwnMembershipRole(role coredata.MembershipRole) { } ` - err = c.Execute(updateQuery, map[string]any{ + err = c.ExecuteConnect(updateQuery, map[string]any{ "input": map[string]any{ "organizationId": c.organizationID.String(), - "memberId": membershipID, + "membershipId": membershipID, "role": string(role), }, }, nil) @@ -270,8 +269,8 @@ func (c *Client) updateOwnMembershipRole(role coredata.MembershipRole) { func (c *Client) inviteMember(email, fullName string, role coredata.MembershipRole) gid.GID { const query = ` - mutation($input: InviteUserInput!) { - inviteUser(input: $input) { + mutation($input: InviteMemberInput!) { + inviteMember(input: $input) { invitationEdge { node { id } } @@ -280,27 +279,26 @@ func (c *Client) inviteMember(email, fullName string, role coredata.MembershipRo ` var result struct { - InviteUser struct { + InviteMember struct { InvitationEdge struct { Node struct { ID string `json:"id"` } `json:"node"` } `json:"invitationEdge"` - } `json:"inviteUser"` + } `json:"inviteMember"` } - err := c.Execute(query, map[string]any{ + err := c.ExecuteConnect(query, map[string]any{ "input": map[string]any{ "organizationId": c.organizationID.String(), "email": email, "fullName": fullName, "role": string(role), - "createPeople": false, }, }, &result) - require.NoError(c.T, err, "inviteUser mutation failed") + require.NoError(c.T, err, "inviteMember mutation failed") - invitationID, err := gid.ParseGID(result.InviteUser.InvitationEdge.Node.ID) + invitationID, err := gid.ParseGID(result.InviteMember.InvitationEdge.Node.ID) require.NoError(c.T, err, "cannot parse invitation ID") return invitationID @@ -310,15 +308,14 @@ func (c *Client) acceptInvitation(invitationID gid.GID) { const query = ` mutation($input: AcceptInvitationInput!) { acceptInvitation(input: $input) { - invitation { - id - status + membershipEdge { + node { id } } } } ` - err := c.Execute(query, map[string]any{ + err := c.ExecuteConnect(query, map[string]any{ "input": map[string]any{ "invitationId": invitationID.String(), }, @@ -326,6 +323,27 @@ func (c *Client) acceptInvitation(invitationID gid.GID) { require.NoError(c.T, err, "acceptInvitation mutation failed") } +func (c *Client) assumeOrganizationSession() { + const query = ` + mutation($input: AssumeOrganizationSessionInput!) { + assumeOrganizationSession(input: $input) { + result { + ... on OrganizationSessionCreated { + session { id } + } + } + } + } + ` + + err := c.ExecuteConnect(query, map[string]any{ + "input": map[string]any{ + "organizationId": c.organizationID.String(), + }, + }, nil) + require.NoError(c.T, err, "assumeOrganizationSession mutation failed") +} + func (c *Client) GetUserID() gid.GID { return c.userID } diff --git a/e2e/internal/testutil/graphql.go b/e2e/internal/testutil/graphql.go index 9652ed929..8d5d734b7 100644 --- a/e2e/internal/testutil/graphql.go +++ b/e2e/internal/testutil/graphql.go @@ -68,7 +68,7 @@ func (e GraphQLErrors) Error() string { return fmt.Sprintf("%s (and %d more errors)", e[0].Message, len(e)-1) } -func (c *Client) Do(query string, variables map[string]any) (*GraphQLResponse, error) { +func (c *Client) doWithEndpoint(endpoint string, query string, variables map[string]any) (*GraphQLResponse, error) { reqBody := GraphQLRequest{ Query: query, Variables: variables, @@ -79,7 +79,7 @@ func (c *Client) Do(query string, variables map[string]any) (*GraphQLResponse, e return nil, fmt.Errorf("cannot marshal request: %w", err) } - req, err := http.NewRequest("POST", c.baseURL+"/api/console/v1/query", bytes.NewReader(body)) + req, err := http.NewRequest("POST", c.baseURL+endpoint, bytes.NewReader(body)) if err != nil { return nil, fmt.Errorf("cannot create request: %w", err) } @@ -112,6 +112,14 @@ func (c *Client) Do(query string, variables map[string]any) (*GraphQLResponse, e return &gqlResp, nil } +func (c *Client) Do(query string, variables map[string]any) (*GraphQLResponse, error) { + return c.doWithEndpoint("/api/console/v1/graphql", query, variables) +} + +func (c *Client) DoConnect(query string, variables map[string]any) (*GraphQLResponse, error) { + return c.doWithEndpoint("/api/connect/v1/graphql", query, variables) +} + func (c *Client) Execute(query string, variables map[string]any, result any) error { resp, err := c.Do(query, variables) if err != nil { @@ -127,6 +135,21 @@ func (c *Client) Execute(query string, variables map[string]any, result any) err return nil } +func (c *Client) ExecuteConnect(query string, variables map[string]any, result any) error { + resp, err := c.DoConnect(query, variables) + if err != nil { + return err + } + + if result != nil && resp.Data != nil { + if err := json.Unmarshal(resp.Data, result); err != nil { + return fmt.Errorf("cannot unmarshal data: %w", err) + } + } + + return nil +} + func (c *Client) MustExecute(query string, variables map[string]any, result any) { c.T.Helper() err := c.Execute(query, variables, result) @@ -226,7 +249,7 @@ func (c *Client) executeMultipart(query string, variables map[string]any, files } // Create request - req, err := http.NewRequest("POST", c.baseURL+"/api/console/v1/query", &buf) + req, err := http.NewRequest("POST", c.baseURL+"/api/console/v1/graphql", &buf) if err != nil { return fmt.Errorf("cannot create request: %w", err) } diff --git a/e2e/internal/testutil/testutil.go b/e2e/internal/testutil/testutil.go index fe39c44af..89bab28f5 100644 --- a/e2e/internal/testutil/testutil.go +++ b/e2e/internal/testutil/testutil.go @@ -114,7 +114,7 @@ func waitForServer(ctx context.Context, baseURL string, timeout time.Duration) e default: } - req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/console/v1/query", nil) + req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/console/v1/graphql", nil) if err != nil { return err }