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