From ebe6192a0c5762e34a82dfd54987f26b63f2c204 Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Fri, 10 Jul 2026 15:15:24 +0200 Subject: [PATCH] Update e2e tests and n8n node for the portal Follow the new domain model in tests: drop organization profile assertions, add a trust center profile test, and hit the dedicated HTTPS listener with SNI for the visitor API. Mirror the custom link rename and profile field moves in the n8n node operations. Signed-off-by: Bryan Frimin --- e2e/console/organization_test.go | 90 +-------------- e2e/console/rbac_test.go | 6 +- e2e/console/trust_center_profile_test.go | 106 ++++++++++++++++++ e2e/internal/testutil/graphql.go | 84 +++++++++++++- e2e/internal/testutil/testutil.go | 14 ++- e2e/mcp/trust_center_test.go | 86 ++++++++------ e2e/trust/trust_center_logo_test.go | 89 ++++++++++++++- .../actions/organization/update.operation.ts | 65 ----------- ...ation.ts => createCustomLink.operation.ts} | 16 +-- ...ation.ts => deleteCustomLink.operation.ts} | 18 +-- .../nodes/Probo/actions/trustCenter/index.ts | 28 ++--- .../actions/trustCenter/update.operation.ts | 65 +++++++++++ 12 files changed, 435 insertions(+), 232 deletions(-) create mode 100644 e2e/console/trust_center_profile_test.go rename packages/n8n-node/nodes/Probo/actions/trustCenter/{createExternalUrl.operation.ts => createCustomLink.operation.ts} (87%) rename packages/n8n-node/nodes/Probo/actions/trustCenter/{deleteExternalUrl.operation.ts => deleteCustomLink.operation.ts} (77%) diff --git a/e2e/console/organization_test.go b/e2e/console/organization_test.go index 6b6f645c9..35fd2e834 100644 --- a/e2e/console/organization_test.go +++ b/e2e/console/organization_test.go @@ -34,7 +34,7 @@ func TestOrganization_Update(t *testing.T) { t.Parallel() owner := testutil.NewClient(t, testutil.RoleOwner) - t.Run("update name and description", func(t *testing.T) { + t.Run("update name", func(t *testing.T) { newName := fmt.Sprintf("Updated Org %d", time.Now().UnixNano()) query := ` @@ -43,7 +43,6 @@ func TestOrganization_Update(t *testing.T) { organization { id name - description } } } @@ -52,9 +51,8 @@ func TestOrganization_Update(t *testing.T) { var result struct { UpdateOrganization struct { Organization struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` + ID string `json:"id"` + Name string `json:"name"` } `json:"organization"` } `json:"updateOrganization"` } @@ -63,82 +61,12 @@ func TestOrganization_Update(t *testing.T) { "input": map[string]any{ "organizationId": owner.GetOrganizationID().String(), "name": newName, - "description": "Updated organization description", }, }, &result) require.NoError(t, err) assert.Equal(t, owner.GetOrganizationID().String(), result.UpdateOrganization.Organization.ID) assert.Equal(t, newName, result.UpdateOrganization.Organization.Name) - assert.Equal(t, "Updated organization description", result.UpdateOrganization.Organization.Description) - }) - - t.Run("update website and email", func(t *testing.T) { - query := ` - mutation UpdateOrganization($input: UpdateOrganizationInput!) { - updateOrganization(input: $input) { - organization { - id - websiteUrl - email - } - } - } - ` - - var result struct { - UpdateOrganization struct { - Organization struct { - ID string `json:"id"` - WebsiteUrl string `json:"websiteUrl"` - Email string `json:"email"` - } `json:"organization"` - } `json:"updateOrganization"` - } - - err := owner.ExecuteConnect(query, map[string]any{ - "input": map[string]any{ - "organizationId": owner.GetOrganizationID().String(), - "websiteUrl": "https://example.com", - "email": "contact@example.com", - }, - }, &result) - require.NoError(t, err) - - assert.Equal(t, "https://example.com", result.UpdateOrganization.Organization.WebsiteUrl) - assert.Equal(t, "contact@example.com", result.UpdateOrganization.Organization.Email) - }) - - t.Run("update headquarter address", func(t *testing.T) { - query := ` - mutation UpdateOrganization($input: UpdateOrganizationInput!) { - updateOrganization(input: $input) { - organization { - id - headquarterAddress - } - } - } - ` - - var result struct { - UpdateOrganization struct { - Organization struct { - ID string `json:"id"` - HeadquarterAddress string `json:"headquarterAddress"` - } `json:"organization"` - } `json:"updateOrganization"` - } - - 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", - }, - }, &result) - require.NoError(t, err) - - assert.Equal(t, "123 Main St, Suite 100, San Francisco, CA 94102", result.UpdateOrganization.Organization.HeadquarterAddress) }) } @@ -200,10 +128,6 @@ func TestOrganization_Get(t *testing.T) { ... on Organization { id name - description - websiteUrl - email - headquarterAddress } } } @@ -211,12 +135,8 @@ func TestOrganization_Get(t *testing.T) { var result struct { Node struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - WebsiteUrl string `json:"websiteUrl"` - Email string `json:"email"` - HeadquarterAddress string `json:"headquarterAddress"` + ID string `json:"id"` + Name string `json:"name"` } `json:"node"` } diff --git a/e2e/console/rbac_test.go b/e2e/console/rbac_test.go index bc58169ed..5cac2f572 100644 --- a/e2e/console/rbac_test.go +++ b/e2e/console/rbac_test.go @@ -1324,7 +1324,7 @@ func TestRBAC(t *testing.T) { client: owner, query: updateOrganizationMutation, variables: func() map[string]any { - return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "description": factory.SafeName("Updated Desc")}} + return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Updated Org")}} }, shouldAllow: true, useConnect: true, @@ -1335,7 +1335,7 @@ func TestRBAC(t *testing.T) { client: admin, query: updateOrganizationMutation, variables: func() map[string]any { - return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "description": factory.SafeName("Updated Desc")}} + return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Updated Org")}} }, shouldAllow: true, useConnect: true, @@ -1346,7 +1346,7 @@ func TestRBAC(t *testing.T) { client: viewer, query: updateOrganizationMutation, variables: func() map[string]any { - return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "description": factory.SafeName("Updated Desc")}} + return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Updated Org")}} }, shouldAllow: false, useConnect: true, diff --git a/e2e/console/trust_center_profile_test.go b/e2e/console/trust_center_profile_test.go new file mode 100644 index 000000000..d3879029d --- /dev/null +++ b/e2e/console/trust_center_profile_test.go @@ -0,0 +1,106 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package console_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.probo.inc/probo/e2e/internal/testutil" +) + +func TestTrustCenter_UpdateProfile(t *testing.T) { + t.Parallel() + + owner := testutil.NewClient(t, testutil.RoleOwner) + organizationID := owner.GetOrganizationID().String() + + const trustCenterQuery = ` + query($organizationId: ID!) { + node(id: $organizationId) { + ... on Organization { + trustCenter { + id + } + } + } + } + ` + + var trustCenterLookup struct { + Node struct { + TrustCenter struct { + ID string `json:"id"` + } `json:"trustCenter"` + } `json:"node"` + } + + err := owner.Execute(trustCenterQuery, map[string]any{ + "organizationId": organizationID, + }, &trustCenterLookup) + require.NoError(t, err) + require.NotEmpty(t, trustCenterLookup.Node.TrustCenter.ID) + + trustCenterID := trustCenterLookup.Node.TrustCenter.ID + + const updateMutation = ` + mutation UpdateTrustCenter($input: UpdateTrustCenterInput!) { + updateTrustCenter(input: $input) { + trustCenter { + id + description + websiteUrl + email + headquarterAddress + } + } + } + ` + + var result struct { + UpdateTrustCenter struct { + TrustCenter struct { + ID string `json:"id"` + Description *string `json:"description"` + WebsiteURL *string `json:"websiteUrl"` + Email *string `json:"email"` + HeadquarterAddress *string `json:"headquarterAddress"` + } `json:"trustCenter"` + } `json:"updateTrustCenter"` + } + + err = owner.Execute(updateMutation, map[string]any{ + "input": map[string]any{ + "trustCenterId": trustCenterID, + "description": "We keep your data safe.", + "websiteUrl": "https://example.com", + "email": "security@example.com", + "headquarterAddress": "123 Main St, San Francisco, CA 94102", + }, + }, &result) + require.NoError(t, err) + + tc := result.UpdateTrustCenter.TrustCenter + assert.Equal(t, trustCenterID, tc.ID) + require.NotNil(t, tc.Description) + assert.Equal(t, "We keep your data safe.", *tc.Description) + require.NotNil(t, tc.WebsiteURL) + assert.Equal(t, "https://example.com", *tc.WebsiteURL) + require.NotNil(t, tc.Email) + assert.Equal(t, "security@example.com", *tc.Email) + require.NotNil(t, tc.HeadquarterAddress) + assert.Equal(t, "123 Main St, San Francisco, CA 94102", *tc.HeadquarterAddress) +} diff --git a/e2e/internal/testutil/graphql.go b/e2e/internal/testutil/graphql.go index 84574bba3..0ec8be32e 100644 --- a/e2e/internal/testutil/graphql.go +++ b/e2e/internal/testutil/graphql.go @@ -22,10 +22,13 @@ package testutil import ( "bytes" + "context" + "crypto/tls" "encoding/json" "fmt" "io" "mime/multipart" + "net" "net/http" "net/textproto" "testing" @@ -34,6 +37,11 @@ import ( "github.com/stretchr/testify/require" ) +// trustCenterHTTPSAddr is the loopback address of the dedicated trust-center +// HTTPS listener started by the e2e probod (see generateConfig). Compliance +// pages are served here exclusively, routed by TLS SNI / Host header. +const trustCenterHTTPSAddr = "127.0.0.1:10443" + type GraphQLRequest struct { Query string `json:"query"` Variables map[string]any `json:"variables,omitempty"` @@ -206,8 +214,76 @@ func ConsoleGraphQLWithAccessToken( return &gqlResp, nil } -func (c *Client) DoTrust(trustCenterID string, query string, variables map[string]any) (*GraphQLResponse, error) { - return c.doWithEndpoint(fmt.Sprintf("/trust/%s/api/trust/v1/graphql", trustCenterID), query, variables) +// trustHTTPClient builds an HTTP client that always dials the dedicated +// trust-center HTTPS listener on loopback while presenting the compliance +// page's host as TLS SNI. Certificates are Pebble-issued for e2e, so +// verification is skipped. +func trustHTTPClient(serverName string) *http.Client { + dialer := &net.Dialer{Timeout: 5 * time.Second} + + return &http.Client{ + Timeout: 30 * time.Second, + Transport: &http.Transport{ + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + return dialer.DialContext(ctx, "tcp", trustCenterHTTPSAddr) + }, + TLSClientConfig: &tls.Config{ + ServerName: serverName, + InsecureSkipVerify: true, //nolint:gosec // e2e talks to Pebble-issued certs on loopback. + }, + }, + } +} + +// DoTrust posts a GraphQL query to a compliance page served on the dedicated +// listener. host is the page's serving domain (a customer custom domain or a +// managed {slug}.probopage.localhost subdomain). +func (c *Client) DoTrust(host string, query string, variables map[string]any) (*GraphQLResponse, error) { + reqBody := GraphQLRequest{ + Query: query, + Variables: variables, + } + + body, err := json.Marshal(reqBody) + if err != nil { + return nil, fmt.Errorf("cannot marshal request: %w", err) + } + + endpoint := fmt.Sprintf("https://%s/api/trust/v1/graphql", host) + + req, err := http.NewRequest("POST", endpoint, bytes.NewReader(body)) + if err != nil { + return nil, fmt.Errorf("cannot create request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := trustHTTPClient(host).Do(req) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + + defer func() { _ = resp.Body.Close() }() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("cannot read response: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(respBody)) + } + + var gqlResp GraphQLResponse + if err := json.Unmarshal(respBody, &gqlResp); err != nil { + return nil, fmt.Errorf("cannot decode response: %w", err) + } + + if len(gqlResp.Errors) > 0 { + return &gqlResp, GraphQLErrors(gqlResp.Errors) + } + + return &gqlResp, nil } func (c *Client) Execute(query string, variables map[string]any, result any) error { @@ -240,8 +316,8 @@ func (c *Client) ExecuteConnect(query string, variables map[string]any, result a return nil } -func (c *Client) ExecuteTrust(trustCenterID string, query string, variables map[string]any, result any) error { - resp, err := c.DoTrust(trustCenterID, query, variables) +func (c *Client) ExecuteTrust(host string, query string, variables map[string]any, result any) error { + resp, err := c.DoTrust(host, query, variables) if err != nil { return err } diff --git a/e2e/internal/testutil/testutil.go b/e2e/internal/testutil/testutil.go index 20f0bb45c..99f4beb52 100644 --- a/e2e/internal/testutil/testutil.go +++ b/e2e/internal/testutil/testutil.go @@ -293,9 +293,17 @@ func generateConfig() (string, error) { "PROBOD_OAUTH2_SERVER_AUTHORIZATION_CODE_DURATION": "5", "PROBOD_OAUTH2_SERVER_DEVICE_CODE_DURATION": "15", - // Trust center. - "PROBOD_TRUST_CENTER_HTTP_ADDR": ":10080", - "PROBOD_TRUST_CENTER_HTTPS_ADDR": ":10443", + // Trust center. Compliance pages are served exclusively over this + // dedicated listener, addressed by Host/SNI. The managed base domain + // yields {slug}.probopage.localhost subdomains for pages without a + // customer custom domain. + "PROBOD_TRUST_CENTER_HTTP_ADDR": ":10080", + "PROBOD_TRUST_CENTER_HTTPS_ADDR": ":10443", + "PROBOD_TRUST_CENTER_BASE_DOMAIN": "probopage.localhost", + + // Keep certificate provisioning snappy so trust-center e2e flows do not + // wait on the default 30s poll (Pebble runs with PEBBLE_VA_ALWAYS_VALID). + "PROBOD_CUSTOM_DOMAINS_PROVISION_INTERVAL": "1", // AWS / S3 (SeaweedFS). "PROBOD_AWS_BUCKET": "probod-test", diff --git a/e2e/mcp/trust_center_test.go b/e2e/mcp/trust_center_test.go index 8934783f4..f5f79c9ba 100644 --- a/e2e/mcp/trust_center_test.go +++ b/e2e/mcp/trust_center_test.go @@ -49,7 +49,7 @@ type trustCenterReference struct { Order int `json:"order"` } -type complianceExternalURL struct { +type complianceCustomLink struct { ID string `json:"id"` Name string `json:"name"` URL string `json:"url"` @@ -174,17 +174,31 @@ func TestMCP_UpdateTrustCenter(t *testing.T) { // Update var updateResult struct { - TrustCenter trustCenter `json:"trustCenter"` + TrustCenter struct { + ID string `json:"id"` + Description *string `json:"description"` + WebsiteURL *string `json:"website_url"` + Email *string `json:"email"` + HeadquarterAddress *string `json:"headquarter_address"` + } `json:"trustCenter"` } mc.CallToolInto("updateTrustCenter", map[string]any{ - "id": getResult.TrustCenter.ID, - "companyName": "Updated Company", - "pageTitle": "Updated Trust Center", + "trust_center_id": getResult.TrustCenter.ID, + "description": "We keep your data safe.", + "website_url": "https://example.com", + "email": "security@example.com", + "headquarter_address": "123 Main St, San Francisco, CA 94102", }, &updateResult) assert.Equal(t, getResult.TrustCenter.ID, updateResult.TrustCenter.ID) - assert.Equal(t, "Updated Company", updateResult.TrustCenter.CompanyName) - assert.Equal(t, "Updated Trust Center", updateResult.TrustCenter.PageTitle) + require.NotNil(t, updateResult.TrustCenter.Description) + assert.Equal(t, "We keep your data safe.", *updateResult.TrustCenter.Description) + require.NotNil(t, updateResult.TrustCenter.WebsiteURL) + assert.Equal(t, "https://example.com", *updateResult.TrustCenter.WebsiteURL) + require.NotNil(t, updateResult.TrustCenter.Email) + assert.Equal(t, "security@example.com", *updateResult.TrustCenter.Email) + require.NotNil(t, updateResult.TrustCenter.HeadquarterAddress) + assert.Equal(t, "123 Main St, San Francisco, CA 94102", *updateResult.TrustCenter.HeadquarterAddress) } func TestMCP_AddTrustCenterReference(t *testing.T) { @@ -363,7 +377,7 @@ func TestMCP_ListTrustCenterFiles(t *testing.T) { assert.NotNil(t, listResult.TrustCenterFiles) } -func TestMCP_AddComplianceExternalURL(t *testing.T) { +func TestMCP_AddComplianceCustomLink(t *testing.T) { t.Parallel() owner := testutil.NewClient(t, testutil.RoleOwner) mc := testutil.NewMCPClient(t, owner) @@ -379,19 +393,19 @@ func TestMCP_AddComplianceExternalURL(t *testing.T) { tcID := getResult.TrustCenter.ID var result struct { - ComplianceExternalURL complianceExternalURL `json:"complianceExternalUrl"` + ComplianceCustomLink complianceCustomLink `json:"complianceCustomLink"` } - mc.CallToolInto("addComplianceExternalURL", map[string]any{ + mc.CallToolInto("addComplianceCustomLink", map[string]any{ "trustCenterId": tcID, "name": "ISO 27001 Certificate", "url": "https://example.com/iso27001", }, &result) - assert.NotEmpty(t, result.ComplianceExternalURL.ID) - assert.Equal(t, "ISO 27001 Certificate", result.ComplianceExternalURL.Name) + assert.NotEmpty(t, result.ComplianceCustomLink.ID) + assert.Equal(t, "ISO 27001 Certificate", result.ComplianceCustomLink.Name) } -func TestMCP_UpdateComplianceExternalURL(t *testing.T) { +func TestMCP_UpdateComplianceCustomLink(t *testing.T) { t.Parallel() owner := testutil.NewClient(t, testutil.RoleOwner) mc := testutil.NewMCPClient(t, owner) @@ -408,30 +422,30 @@ func TestMCP_UpdateComplianceExternalURL(t *testing.T) { // Create var addResult struct { - ComplianceExternalURL complianceExternalURL `json:"complianceExternalUrl"` + ComplianceCustomLink complianceCustomLink `json:"complianceCustomLink"` } - mc.CallToolInto("addComplianceExternalURL", map[string]any{ + mc.CallToolInto("addComplianceCustomLink", map[string]any{ "trustCenterId": tcID, "name": "Original URL", "url": "https://example.com/original", }, &addResult) - require.NotEmpty(t, addResult.ComplianceExternalURL.ID) + require.NotEmpty(t, addResult.ComplianceCustomLink.ID) // Update var updateResult struct { - ComplianceExternalURL complianceExternalURL `json:"complianceExternalUrl"` + ComplianceCustomLink complianceCustomLink `json:"complianceCustomLink"` } - mc.CallToolInto("updateComplianceExternalURL", map[string]any{ - "id": addResult.ComplianceExternalURL.ID, + mc.CallToolInto("updateComplianceCustomLink", map[string]any{ + "id": addResult.ComplianceCustomLink.ID, "name": "Updated URL", "url": "https://example.com/updated", }, &updateResult) - assert.Equal(t, addResult.ComplianceExternalURL.ID, updateResult.ComplianceExternalURL.ID) - assert.Equal(t, "Updated URL", updateResult.ComplianceExternalURL.Name) + assert.Equal(t, addResult.ComplianceCustomLink.ID, updateResult.ComplianceCustomLink.ID) + assert.Equal(t, "Updated URL", updateResult.ComplianceCustomLink.Name) } -func TestMCP_DeleteComplianceExternalURL(t *testing.T) { +func TestMCP_DeleteComplianceCustomLink(t *testing.T) { t.Parallel() owner := testutil.NewClient(t, testutil.RoleOwner) mc := testutil.NewMCPClient(t, owner) @@ -448,27 +462,27 @@ func TestMCP_DeleteComplianceExternalURL(t *testing.T) { // Create var addResult struct { - ComplianceExternalURL complianceExternalURL `json:"complianceExternalUrl"` + ComplianceCustomLink complianceCustomLink `json:"complianceCustomLink"` } - mc.CallToolInto("addComplianceExternalURL", map[string]any{ + mc.CallToolInto("addComplianceCustomLink", map[string]any{ "trustCenterId": tcID, "name": "URL to delete", "url": "https://example.com/delete", }, &addResult) - require.NotEmpty(t, addResult.ComplianceExternalURL.ID) + require.NotEmpty(t, addResult.ComplianceCustomLink.ID) // Delete var deleteResult struct { - DeletedComplianceExternalURLID string `json:"deletedComplianceExternalUrlId"` + DeletedComplianceCustomLinkID string `json:"deletedComplianceCustomLinkId"` } - mc.CallToolInto("deleteComplianceExternalURL", map[string]any{ - "id": addResult.ComplianceExternalURL.ID, + mc.CallToolInto("deleteComplianceCustomLink", map[string]any{ + "id": addResult.ComplianceCustomLink.ID, }, &deleteResult) - assert.Equal(t, addResult.ComplianceExternalURL.ID, deleteResult.DeletedComplianceExternalURLID) + assert.Equal(t, addResult.ComplianceCustomLink.ID, deleteResult.DeletedComplianceCustomLinkID) } -func TestMCP_ListComplianceExternalURLs(t *testing.T) { +func TestMCP_ListComplianceCustomLinks(t *testing.T) { t.Parallel() owner := testutil.NewClient(t, testutil.RoleOwner) mc := testutil.NewMCPClient(t, owner) @@ -486,25 +500,25 @@ func TestMCP_ListComplianceExternalURLs(t *testing.T) { // Create URLs for i := range 2 { var result struct { - ComplianceExternalURL complianceExternalURL `json:"complianceExternalUrl"` + ComplianceCustomLink complianceCustomLink `json:"complianceCustomLink"` } - mc.CallToolInto("addComplianceExternalURL", map[string]any{ + mc.CallToolInto("addComplianceCustomLink", map[string]any{ "trustCenterId": tcID, "name": factory.SafeName("URL"), "url": "https://example.com/" + factory.SafeName("path"), }, &result) - require.NotEmpty(t, result.ComplianceExternalURL.ID) + require.NotEmpty(t, result.ComplianceCustomLink.ID) _ = i } // List var listResult struct { - ComplianceExternalURLs []complianceExternalURL `json:"complianceExternalUrls"` + ComplianceCustomLinks []complianceCustomLink `json:"complianceCustomLinks"` } - mc.CallToolInto("listComplianceExternalURLs", map[string]any{ + mc.CallToolInto("listComplianceCustomLinks", map[string]any{ "trustCenterId": tcID, }, &listResult) - assert.GreaterOrEqual(t, len(listResult.ComplianceExternalURLs), 2) + assert.GreaterOrEqual(t, len(listResult.ComplianceCustomLinks), 2) } diff --git a/e2e/trust/trust_center_logo_test.go b/e2e/trust/trust_center_logo_test.go index ac98bdfdc..98e772e71 100644 --- a/e2e/trust/trust_center_logo_test.go +++ b/e2e/trust/trust_center_logo_test.go @@ -23,6 +23,7 @@ package trust_test import ( "io" "net/http" + "net/url" "strings" "testing" "time" @@ -36,8 +37,76 @@ func TestTrustCenter_LogoFileDownloadURL(t *testing.T) { t.Parallel() owner := testutil.NewClient(t, testutil.RoleOwner) - trustCenterID := lookupTrustCenterID(t, owner) - activateTrustCenter(t, owner, trustCenterID) + organizationID := owner.GetOrganizationID().String() + + const trustCenterQuery = ` + query($organizationId: ID!) { + node(id: $organizationId) { + ... on Organization { + trustCenter { + id + } + } + } + } + ` + + var trustCenterLookup struct { + Node struct { + TrustCenter struct { + ID string `json:"id"` + } `json:"trustCenter"` + } `json:"node"` + } + + err := owner.Execute(trustCenterQuery, map[string]any{ + "organizationId": organizationID, + }, &trustCenterLookup) + require.NoError(t, err) + require.NotEmpty(t, trustCenterLookup.Node.TrustCenter.ID) + + trustCenterID := trustCenterLookup.Node.TrustCenter.ID + + const activateMutation = ` + mutation($input: UpdateTrustCenterInput!) { + updateTrustCenter(input: $input) { + trustCenter { + id + active + publicUrl + } + } + } + ` + + var activateResult struct { + UpdateTrustCenter struct { + TrustCenter struct { + ID string `json:"id"` + Active bool `json:"active"` + PublicURL string `json:"publicUrl"` + } `json:"trustCenter"` + } `json:"updateTrustCenter"` + } + + err = owner.Execute(activateMutation, map[string]any{ + "input": map[string]any{ + "trustCenterId": trustCenterID, + "active": true, + }, + }, &activateResult) + require.NoError(t, err) + + // Publishing the page provisions a managed {slug}.probopage.localhost + // domain; the effective public URL resolves to it while no customer + // custom domain is primary. + require.NotEmpty(t, activateResult.UpdateTrustCenter.TrustCenter.PublicURL) + + publicURL, err := url.Parse(activateResult.UpdateTrustCenter.TrustCenter.PublicURL) + require.NoError(t, err) + + trustHost := publicURL.Host + require.NotEmpty(t, trustHost) const uploadMutation = ` mutation UpdateTrustCenterBrand($input: UpdateTrustCenterBrandInput!) { @@ -79,7 +148,7 @@ func TestTrustCenter_LogoFileDownloadURL(t *testing.T) { } `json:"updateTrustCenterBrand"` } - err := owner.ExecuteWithFile(uploadMutation, map[string]any{ + err = owner.ExecuteWithFile(uploadMutation, map[string]any{ "input": map[string]any{ "trustCenterId": trustCenterID, "logoFile": nil, @@ -114,8 +183,18 @@ func TestTrustCenter_LogoFileDownloadURL(t *testing.T) { } `json:"currentTrustCenter"` } - err = owner.ExecuteTrust(trustCenterID, trustGraphQLQuery, nil, &trustResult) - require.NoError(t, err) + // The dedicated HTTPS listener only serves the page once the managed + // domain's certificate has been provisioned (async, ~1s poll in e2e), so + // retry until the TLS handshake and query succeed. + require.Eventually(t, func() bool { + trustResult.CurrentTrustCenter.Logo = nil + if err := owner.ExecuteTrust(trustHost, trustGraphQLQuery, nil, &trustResult); err != nil { + return false + } + + return trustResult.CurrentTrustCenter.Logo != nil + }, 30*time.Second, 500*time.Millisecond, "trust center did not become servable on the dedicated listener") + require.NotNil(t, trustResult.CurrentTrustCenter.Logo) assert.Equal(t, uploadResult.UpdateTrustCenterBrand.TrustCenter.Logo.ID, trustResult.CurrentTrustCenter.Logo.ID) diff --git a/packages/n8n-node/nodes/Probo/actions/organization/update.operation.ts b/packages/n8n-node/nodes/Probo/actions/organization/update.operation.ts index 25376fbf1..5ad3d9ee2 100644 --- a/packages/n8n-node/nodes/Probo/actions/organization/update.operation.ts +++ b/packages/n8n-node/nodes/Probo/actions/organization/update.operation.ts @@ -49,59 +49,6 @@ export const description: INodeProperties[] = [ default: '', description: 'The name of the organization', }, - { - displayName: 'Description', - name: 'description', - type: 'string', - displayOptions: { - show: { - resource: ['organization'], - operation: ['update'], - }, - }, - default: '', - description: 'The description of the organization', - }, - { - displayName: 'Website URL', - name: 'websiteUrl', - type: 'string', - displayOptions: { - show: { - resource: ['organization'], - operation: ['update'], - }, - }, - default: '', - description: 'The website URL of the organization', - }, - { - displayName: 'Email', - name: 'email', - type: 'string', - placeholder: 'name@example.com', - displayOptions: { - show: { - resource: ['organization'], - operation: ['update'], - }, - }, - default: '', - description: 'The email address of the organization', - }, - { - displayName: 'Headquarter Address', - name: 'headquarterAddress', - type: 'string', - displayOptions: { - show: { - resource: ['organization'], - operation: ['update'], - }, - }, - default: '', - description: 'The headquarter address of the organization', - }, ]; export async function execute( @@ -110,10 +57,6 @@ export async function execute( ): Promise { const organizationId = this.getNodeParameter('organizationId', itemIndex) as string; const name = this.getNodeParameter('name', itemIndex, '') as string; - const description = this.getNodeParameter('description', itemIndex, '') as string; - const websiteUrl = this.getNodeParameter('websiteUrl', itemIndex, '') as string; - const email = this.getNodeParameter('email', itemIndex, '') as string; - const headquarterAddress = this.getNodeParameter('headquarterAddress', itemIndex, '') as string; const query = ` mutation UpdateOrganization($input: UpdateOrganizationInput!) { @@ -121,10 +64,6 @@ export async function execute( organization { id name - description - websiteUrl - email - headquarterAddress logo { id fileName @@ -144,10 +83,6 @@ export async function execute( const input: Record = { organizationId }; if (name) input.name = name; - if (description) input.description = description; - if (websiteUrl) input.websiteUrl = websiteUrl; - if (email) input.email = email; - if (headquarterAddress) input.headquarterAddress = headquarterAddress; const responseData = await proboConnectApiRequest.call(this, query, { input }); diff --git a/packages/n8n-node/nodes/Probo/actions/trustCenter/createExternalUrl.operation.ts b/packages/n8n-node/nodes/Probo/actions/trustCenter/createCustomLink.operation.ts similarity index 87% rename from packages/n8n-node/nodes/Probo/actions/trustCenter/createExternalUrl.operation.ts rename to packages/n8n-node/nodes/Probo/actions/trustCenter/createCustomLink.operation.ts index aa2fb4ddb..751014ac8 100644 --- a/packages/n8n-node/nodes/Probo/actions/trustCenter/createExternalUrl.operation.ts +++ b/packages/n8n-node/nodes/Probo/actions/trustCenter/createCustomLink.operation.ts @@ -29,7 +29,7 @@ export const description: INodeProperties[] = [ displayOptions: { show: { resource: ['trustCenter'], - operation: ['createExternalUrl'], + operation: ['createCustomLink'], }, }, default: '', @@ -43,11 +43,11 @@ export const description: INodeProperties[] = [ displayOptions: { show: { resource: ['trustCenter'], - operation: ['createExternalUrl'], + operation: ['createCustomLink'], }, }, default: '', - description: 'The name of the external URL', + description: 'The name of the custom link', required: true, }, { @@ -57,11 +57,11 @@ export const description: INodeProperties[] = [ displayOptions: { show: { resource: ['trustCenter'], - operation: ['createExternalUrl'], + operation: ['createCustomLink'], }, }, default: '', - description: 'The external URL', + description: 'The custom link', required: true, }, ]; @@ -75,9 +75,9 @@ export async function execute( const url = this.getNodeParameter('url', itemIndex) as string; const query = ` - mutation CreateComplianceExternalURL($input: CreateComplianceExternalURLInput!) { - createComplianceExternalURL(input: $input) { - complianceExternalURLEdge { + mutation CreateComplianceCustomLink($input: CreateComplianceCustomLinkInput!) { + createComplianceCustomLink(input: $input) { + complianceCustomLinkEdge { node { id name diff --git a/packages/n8n-node/nodes/Probo/actions/trustCenter/deleteExternalUrl.operation.ts b/packages/n8n-node/nodes/Probo/actions/trustCenter/deleteCustomLink.operation.ts similarity index 77% rename from packages/n8n-node/nodes/Probo/actions/trustCenter/deleteExternalUrl.operation.ts rename to packages/n8n-node/nodes/Probo/actions/trustCenter/deleteCustomLink.operation.ts index 2dd9f1e06..30d1e0739 100644 --- a/packages/n8n-node/nodes/Probo/actions/trustCenter/deleteExternalUrl.operation.ts +++ b/packages/n8n-node/nodes/Probo/actions/trustCenter/deleteCustomLink.operation.ts @@ -23,17 +23,17 @@ import { proboApiRequest } from '../../GenericFunctions'; export const description: INodeProperties[] = [ { - displayName: 'Compliance External URL ID', - name: 'complianceExternalUrlId', + displayName: 'Compliance Custom Link ID', + name: 'complianceCustomLinkId', type: 'string', displayOptions: { show: { resource: ['trustCenter'], - operation: ['deleteExternalUrl'], + operation: ['deleteCustomLink'], }, }, default: '', - description: 'The ID of the compliance external URL to delete', + description: 'The ID of the compliance custom link to delete', required: true, }, ]; @@ -42,17 +42,17 @@ export async function execute( this: IExecuteFunctions, itemIndex: number, ): Promise { - const complianceExternalUrlId = this.getNodeParameter('complianceExternalUrlId', itemIndex) as string; + const complianceCustomLinkId = this.getNodeParameter('complianceCustomLinkId', itemIndex) as string; const query = ` - mutation DeleteComplianceExternalURL($input: DeleteComplianceExternalURLInput!) { - deleteComplianceExternalURL(input: $input) { - deletedComplianceExternalURLId + mutation DeleteComplianceCustomLink($input: DeleteComplianceCustomLinkInput!) { + deleteComplianceCustomLink(input: $input) { + deletedComplianceCustomLinkId } } `; - const responseData = await proboApiRequest.call(this, query, { input: { id: complianceExternalUrlId } }); + const responseData = await proboApiRequest.call(this, query, { input: { id: complianceCustomLinkId } }); return { json: responseData, diff --git a/packages/n8n-node/nodes/Probo/actions/trustCenter/index.ts b/packages/n8n-node/nodes/Probo/actions/trustCenter/index.ts index e4dcb0534..0ac7791cd 100644 --- a/packages/n8n-node/nodes/Probo/actions/trustCenter/index.ts +++ b/packages/n8n-node/nodes/Probo/actions/trustCenter/index.ts @@ -26,8 +26,8 @@ import * as createReferenceOp from './createReference.operation'; import * as deleteReferenceOp from './deleteReference.operation'; import * as getAllFilesOp from './getAllFiles.operation'; import * as deleteFileOp from './deleteFile.operation'; -import * as createExternalUrlOp from './createExternalUrl.operation'; -import * as deleteExternalUrlOp from './deleteExternalUrl.operation'; +import * as createCustomLinkOp from './createCustomLink.operation'; +import * as deleteCustomLinkOp from './deleteCustomLink.operation'; import * as getAllCommitmentGroupsOp from './getAllCommitmentGroups.operation'; import * as createCommitmentGroupOp from './createCommitmentGroup.operation'; import * as updateCommitmentGroupOp from './updateCommitmentGroup.operation'; @@ -62,10 +62,10 @@ export const description: INodeProperties[] = [ action: 'Create a compliance portal commitment group', }, { - name: 'Create External URL', - value: 'createExternalUrl', - description: 'Create a new compliance external URL', - action: 'Create a compliance external URL', + name: 'Create Custom Link', + value: 'createCustomLink', + description: 'Create a new compliance custom link', + action: 'Create a compliance custom link', }, { name: 'Create Reference', @@ -86,10 +86,10 @@ export const description: INodeProperties[] = [ action: 'Delete a compliance portal commitment group', }, { - name: 'Delete External URL', - value: 'deleteExternalUrl', - description: 'Delete a compliance external URL', - action: 'Delete a compliance external URL', + name: 'Delete Custom Link', + value: 'deleteCustomLink', + description: 'Delete a compliance custom link', + action: 'Delete a compliance custom link', }, { name: 'Delete File', @@ -161,8 +161,8 @@ export const description: INodeProperties[] = [ ...deleteReferenceOp.description, ...getAllFilesOp.description, ...deleteFileOp.description, - ...createExternalUrlOp.description, - ...deleteExternalUrlOp.description, + ...createCustomLinkOp.description, + ...deleteCustomLinkOp.description, ...getAllCommitmentGroupsOp.description, ...createCommitmentGroupOp.description, ...updateCommitmentGroupOp.description, @@ -181,8 +181,8 @@ export { deleteReferenceOp as deleteReference, getAllFilesOp as getAllFiles, deleteFileOp as deleteFile, - createExternalUrlOp as createExternalUrl, - deleteExternalUrlOp as deleteExternalUrl, + createCustomLinkOp as createCustomLink, + deleteCustomLinkOp as deleteCustomLink, getAllCommitmentGroupsOp as getAllCommitmentGroups, createCommitmentGroupOp as createCommitmentGroup, updateCommitmentGroupOp as updateCommitmentGroup, diff --git a/packages/n8n-node/nodes/Probo/actions/trustCenter/update.operation.ts b/packages/n8n-node/nodes/Probo/actions/trustCenter/update.operation.ts index e63085efe..3d07ad54d 100644 --- a/packages/n8n-node/nodes/Probo/actions/trustCenter/update.operation.ts +++ b/packages/n8n-node/nodes/Probo/actions/trustCenter/update.operation.ts @@ -76,6 +76,59 @@ export const description: INodeProperties[] = [ default: '', description: 'Whether search engines should index the trust center', }, + { + displayName: 'Description', + name: 'description', + type: 'string', + displayOptions: { + show: { + resource: ['trustCenter'], + operation: ['update'], + }, + }, + default: '', + description: 'The description shown on the compliance page', + }, + { + displayName: 'Website URL', + name: 'websiteUrl', + type: 'string', + displayOptions: { + show: { + resource: ['trustCenter'], + operation: ['update'], + }, + }, + default: '', + description: 'The website URL shown on the compliance page', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + placeholder: 'name@example.com', + displayOptions: { + show: { + resource: ['trustCenter'], + operation: ['update'], + }, + }, + default: '', + description: 'The contact email shown on the compliance page', + }, + { + displayName: 'Headquarter Address', + name: 'headquarterAddress', + type: 'string', + displayOptions: { + show: { + resource: ['trustCenter'], + operation: ['update'], + }, + }, + default: '', + description: 'The headquarter address shown on the compliance page', + }, ]; export async function execute( @@ -85,6 +138,10 @@ export async function execute( const trustCenterId = this.getNodeParameter('trustCenterId', itemIndex) as string; const active = this.getNodeParameter('active', itemIndex) as boolean | undefined; const searchEngineIndexing = this.getNodeParameter('searchEngineIndexing', itemIndex, '') as string; + const description = this.getNodeParameter('description', itemIndex, '') as string; + const websiteUrl = this.getNodeParameter('websiteUrl', itemIndex, '') as string; + const email = this.getNodeParameter('email', itemIndex, '') as string; + const headquarterAddress = this.getNodeParameter('headquarterAddress', itemIndex, '') as string; const query = ` mutation UpdateTrustCenter($input: UpdateTrustCenterInput!) { @@ -93,6 +150,10 @@ export async function execute( id active searchEngineIndexing + description + websiteUrl + email + headquarterAddress createdAt updatedAt } @@ -103,6 +164,10 @@ export async function execute( const input: Record = { trustCenterId }; if (active !== undefined) input.active = active; if (searchEngineIndexing) input.searchEngineIndexing = searchEngineIndexing; + if (description) input.description = description; + if (websiteUrl) input.websiteUrl = websiteUrl; + if (email) input.email = email; + if (headquarterAddress) input.headquarterAddress = headquarterAddress; const responseData = await proboApiRequest.call(this, query, { input });