diff --git a/packages/manager/.changeset/pr-11002-tests-1727812216912.md b/packages/manager/.changeset/pr-11002-tests-1727812216912.md new file mode 100644 index 00000000000..c2f42f4bb92 --- /dev/null +++ b/packages/manager/.changeset/pr-11002-tests-1727812216912.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tests +--- + +Add integration test for Object Storage Gen2 Properties tab ([#11002](https://github.com/linode/manager/pull/11002)) diff --git a/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-create-gen2.spec.ts b/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-create-gen2.spec.ts index dce48e600f3..7eeea7167ee 100644 --- a/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-create-gen2.spec.ts +++ b/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-create-gen2.spec.ts @@ -8,6 +8,7 @@ import { } from 'support/intercepts/object-storage'; import { mockGetRegions } from 'support/intercepts/regions'; import { ui } from 'support/ui'; +import { checkRateLimitsTable } from 'support/util/object-storage-gen2'; import { randomLabel } from 'support/util/random'; import { accountFactory, @@ -16,10 +17,7 @@ import { regionFactory, } from 'src/factories'; import { chooseRegion } from 'support/util/regions'; -import type { - ObjectStorageEndpoint, - ObjectStorageEndpointTypes, -} from '@linode/api-v4'; +import type { ObjectStorageEndpoint } from '@linode/api-v4'; describe('Object Storage Gen2 create bucket tests', () => { beforeEach(() => { @@ -72,36 +70,6 @@ describe('Object Storage Gen2 create bucket tests', () => { }), ]; - const checkRateLimitsTable = (endpointType: ObjectStorageEndpointTypes) => { - const expectedHeaders = ['Limits', 'GET', 'PUT', 'LIST', 'DELETE', 'OTHER']; - const expectedBasicValues = ['Basic', '2,000', '500', '100', '200', '400']; - const expectedHighValues = - endpointType === 'E3' - ? ['High', '20,000', '2,000', '400', '400', '1,000'] - : ['High', '5,000', '1,000', '200', '200', '800']; - - cy.get('[data-testid="bucket-rate-limit-table"]').within(() => { - expectedHeaders.forEach((header, index) => { - cy.get('th').eq(index).should('contain.text', header); - }); - - cy.contains('tr', 'Basic').within(() => { - expectedBasicValues.forEach((value, index) => { - cy.get('td').eq(index).should('contain.text', value); - }); - }); - - cy.contains('tr', 'High').within(() => { - expectedHighValues.forEach((value, index) => { - cy.get('td').eq(index).should('contain.text', value); - }); - }); - - // Check that Basic radio button is checked - cy.findByLabelText('Basic').should('be.checked'); - }); - }; - /** * Confirms UI flow for creating a gen2 Object Storage bucket with endpoint E0 * Confirms all endpoints are displayed regardless if there's multiple of the same type diff --git a/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-details-gen2.spec.ts b/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-details-gen2.spec.ts index c7302697565..0c29d1aecd8 100644 --- a/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-details-gen2.spec.ts +++ b/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-details-gen2.spec.ts @@ -5,6 +5,8 @@ import { mockGetObjectStorageEndpoints, mockGetBucketAccess, } from 'support/intercepts/object-storage'; +import { checkRateLimitsTable } from 'support/util/object-storage-gen2'; +import { ui } from 'support/ui'; import { accountFactory, objectStorageBucketFactoryGen2, @@ -13,7 +15,7 @@ import { } from 'src/factories'; import { ACLType, ObjectStorageEndpointTypes } from '@linode/api-v4'; -describe('Object Storage Gen 2 bucket details Access and SSL/TLS tabs', () => { +describe('Object Storage Gen 2 bucket details tabs', () => { beforeEach(() => { mockAppendFeatureFlags({ objMultiCluster: true, @@ -56,87 +58,181 @@ describe('Object Storage Gen 2 bucket details Access and SSL/TLS tabs', () => { return { mockBucket, mockEndpoint }; }; - ['E0', 'E1'].forEach((endpoint: ObjectStorageEndpointTypes) => { - /** - * Parameterized test for object storage endpoint types E0 and E1 - * - Confirms the CORS toggle still appears - * - Confirms the SSL/TLS tab appears - */ - it(`does not hide the CORS toggle and SSL/TLS tab for buckets with an ${endpoint} endpoint`, () => { - const { mockBucket, mockEndpoint } = createMocksBasedOnEndpointType( - endpoint - ); - const { cluster, label } = mockBucket; - - mockGetBucketAccess(label, cluster, mockAccess).as('getBucketAccess'); - mockGetBucketsForRegion(mockRegion.id, [mockBucket]).as( - 'getBucketsForRegion' - ); - mockGetObjectStorageEndpoints([mockEndpoint]).as( - 'getObjectStorageEndpoints' - ); - - cy.visitWithLogin(`/object-storage/buckets/${cluster}/${label}/access`); - cy.wait([ - '@getFeatureFlags', - '@getAccount', - '@getObjectStorageEndpoints', - '@getBucketsForRegion', - '@getBucketAccess', - ]); - - cy.findByText('Bucket Access').should('be.visible'); - cy.findByLabelText('Access Control List (ACL)').should('be.visible'); - // confirm CORS is visible - cy.findByText('CORS Enabled').should('be.visible'); - cy.contains( - 'Whether Cross-Origin Resource Sharing is enabled for all origins. For more fine-grained control of CORS, please use another S3-compatible tool.' - ).should('be.visible'); - - // Confirm SSL/TLS tab is not hidden and is clickable - cy.findByText('SSL/TLS').should('be.visible').click(); - cy.url().should('endWith', '/ssl'); + describe('Access and SSL/TLS tabs', () => { + ['E0', 'E1'].forEach((endpoint: ObjectStorageEndpointTypes) => { + /** + * Parameterized test for object storage endpoint types E0 and E1 + * - Confirms the CORS toggle still appears + * - Confirms the SSL/TLS tab appears + */ + it(`does not hide the CORS toggle and SSL/TLS tab for buckets with an ${endpoint} endpoint`, () => { + const { mockBucket, mockEndpoint } = createMocksBasedOnEndpointType( + endpoint + ); + const { cluster, label } = mockBucket; + + mockGetBucketAccess(label, cluster, mockAccess).as('getBucketAccess'); + mockGetBucketsForRegion(mockRegion.id, [mockBucket]).as( + 'getBucketsForRegion' + ); + mockGetObjectStorageEndpoints([mockEndpoint]).as( + 'getObjectStorageEndpoints' + ); + + cy.visitWithLogin(`/object-storage/buckets/${cluster}/${label}/access`); + cy.wait([ + '@getFeatureFlags', + '@getAccount', + '@getObjectStorageEndpoints', + '@getBucketsForRegion', + '@getBucketAccess', + ]); + + cy.findByText('Bucket Access').should('be.visible'); + cy.findByLabelText('Access Control List (ACL)').should('be.visible'); + // confirm CORS is visible + cy.findByText('CORS Enabled').should('be.visible'); + cy.contains( + 'Whether Cross-Origin Resource Sharing is enabled for all origins. For more fine-grained control of CORS, please use another S3-compatible tool.' + ).should('be.visible'); + + // Confirm SSL/TLS tab is not hidden and is clickable + cy.findByText('SSL/TLS').should('be.visible').click(); + cy.url().should('endWith', '/ssl'); + }); + }); + + ['E2', 'E3'].forEach((endpoint: ObjectStorageEndpointTypes) => { + /** + * Parameterized test for object storage endpoint types E2 and E3 + * - Confirms the CORS toggle is hidden + * - Confirms the SSL/TLS tab is hidden + */ + it(`hides the CORS toggle and SSL/TLS tab for for buckets with an ${endpoint} endpoint`, () => { + const { mockBucket, mockEndpoint } = createMocksBasedOnEndpointType( + endpoint + ); + const { cluster, label } = mockBucket; + + mockGetBucketAccess(label, cluster, mockAccess).as('getBucketAccess'); + mockGetBucketsForRegion(mockRegion.id, [mockBucket]).as( + 'getBucketsForRegion' + ); + mockGetObjectStorageEndpoints([mockEndpoint]).as( + 'getObjectStorageEndpoints' + ); + + cy.visitWithLogin(`/object-storage/buckets/${cluster}/${label}/access`); + cy.wait([ + '@getFeatureFlags', + '@getAccount', + '@getObjectStorageEndpoints', + '@getBucketsForRegion', + '@getBucketAccess', + ]); + + cy.findByText('Bucket Access').should('be.visible'); + cy.findByLabelText('Access Control List (ACL)').should('be.visible'); + // confirm CORS is not visible + cy.contains( + 'CORS (Cross Origin Sharing) is not available for endpoint types E2 and E3' + ).should('be.visible'); + + // confirms the SSL/TLS tab is not present + cy.findByText('SSL/TLS').should('not.exist'); + }); }); }); - ['E2', 'E3'].forEach((endpoint: ObjectStorageEndpointTypes) => { - /** - * Parameterized test for object storage endpoint types E2 and E3 - * - Confirms the CORS toggle is hidden - * - Confirms the SSL/TLS tab is hidden - */ - it(`hides the CORS toggle and SSL/TLS tab for for buckets with an ${endpoint} endpoint`, () => { - const { mockBucket, mockEndpoint } = createMocksBasedOnEndpointType( - endpoint - ); - const { cluster, label } = mockBucket; - - mockGetBucketAccess(label, cluster, mockAccess).as('getBucketAccess'); - mockGetBucketsForRegion(mockRegion.id, [mockBucket]).as( - 'getBucketsForRegion' - ); - mockGetObjectStorageEndpoints([mockEndpoint]).as( - 'getObjectStorageEndpoints' - ); - - cy.visitWithLogin(`/object-storage/buckets/${cluster}/${label}/access`); - cy.wait([ - '@getFeatureFlags', - '@getAccount', - '@getObjectStorageEndpoints', - '@getBucketsForRegion', - '@getBucketAccess', - ]); - - cy.findByText('Bucket Access').should('be.visible'); - cy.findByLabelText('Access Control List (ACL)').should('be.visible'); - // confirm CORS is not visible - cy.contains( - 'CORS (Cross Origin Sharing) is not available for endpoint types E2 and E3' - ).should('be.visible'); - - // confirms the SSL/TLS tab is not present - cy.findByText('SSL/TLS').should('not.exist'); + describe('Properties tab', () => { + ['E0', 'E1'].forEach((endpoint: ObjectStorageEndpointTypes) => { + /** + * Parameterized test for buckets with endpoint types of E0 and E1 + * - Confirms the Properties tab is visible + * - Confirms there is no bucket rate limits table + */ + it(`confirms the Properties tab does not have a rate limits table for buckets with endpoint type ${endpoint}`, () => { + const { mockBucket, mockEndpoint } = createMocksBasedOnEndpointType( + endpoint + ); + const { cluster, label } = mockBucket; + + mockGetBucketsForRegion(mockRegion.id, [mockBucket]).as( + 'getBucketsForRegion' + ); + mockGetObjectStorageEndpoints([mockEndpoint]).as( + 'getObjectStorageEndpoints' + ); + + cy.visitWithLogin( + `/object-storage/buckets/${cluster}/${label}/properties` + ); + cy.wait([ + '@getFeatureFlags', + '@getAccount', + '@getObjectStorageEndpoints', + '@getBucketsForRegion', + ]); + + cy.findByText('Bucket Rate Limits').should('be.visible'); + // confirms helper text + cy.contains( + 'This endpoint type supports up to 750 Requests Per Second (RPS). Understand bucket rate limits' + ).should('be.visible'); + + // confirm bucket rate limit table should not exist + cy.get('[data-testid="bucket-rate-limit-table"]').should('not.exist'); + + // confirm 'Save' button is disabled (for now) + ui.button + .findByAttribute('label', 'Save') + .should('be.visible') + .should('be.disabled'); + }); + }); + + ['E2', 'E3'].forEach((endpoint: ObjectStorageEndpointTypes) => { + /** + * Parameterized test for buckets with endpoint types of E2 and E3 + * - Confirms the Properties tab is visible + * - Confirms bucket rates limit table exists + */ + it(`confirms the Properties tab and rate limits table for buckets with endpoint type ${endpoint}`, () => { + const { mockBucket, mockEndpoint } = createMocksBasedOnEndpointType( + endpoint + ); + const { cluster, label } = mockBucket; + + mockGetBucketsForRegion(mockRegion.id, [mockBucket]).as( + 'getBucketsForRegion' + ); + mockGetObjectStorageEndpoints([mockEndpoint]).as( + 'getObjectStorageEndpoints' + ); + + cy.visitWithLogin( + `/object-storage/buckets/${cluster}/${label}/properties` + ); + cy.wait([ + '@getFeatureFlags', + '@getAccount', + '@getObjectStorageEndpoints', + '@getBucketsForRegion', + ]); + + cy.findByText('Bucket Rate Limits'); + cy.contains( + 'Specifies the maximum Requests Per Second (RPS) for a bucket. To increase it to High, open a support ticket. Understand bucket rate limits.' + ).should('be.visible'); + // Confirm bucket rate limits table exists with correct values + checkRateLimitsTable(endpoint); + + // confirm 'Save' button is disabled (for now) + ui.button + .findByAttribute('label', 'Save') + .should('be.visible') + .should('be.disabled'); + }); }); }); }); diff --git a/packages/manager/cypress/support/util/object-storage-gen2.ts b/packages/manager/cypress/support/util/object-storage-gen2.ts new file mode 100644 index 00000000000..91d492638f4 --- /dev/null +++ b/packages/manager/cypress/support/util/object-storage-gen2.ts @@ -0,0 +1,33 @@ +import type { ObjectStorageEndpointTypes } from '@linode/api-v4'; + +export const checkRateLimitsTable = ( + endpointType: ObjectStorageEndpointTypes +) => { + const expectedHeaders = ['Limits', 'GET', 'PUT', 'LIST', 'DELETE', 'OTHER']; + const expectedBasicValues = ['Basic', '2,000', '500', '100', '200', '400']; + const expectedHighValues = + endpointType === 'E3' + ? ['High', '20,000', '2,000', '400', '400', '1,000'] + : ['High', '5,000', '1,000', '200', '200', '800']; + + cy.get('[data-testid="bucket-rate-limit-table"]').within(() => { + expectedHeaders.forEach((header, index) => { + cy.get('th').eq(index).should('contain.text', header); + }); + + cy.contains('tr', 'Basic').within(() => { + expectedBasicValues.forEach((value, index) => { + cy.get('td').eq(index).should('contain.text', value); + }); + }); + + cy.contains('tr', 'High').within(() => { + expectedHighValues.forEach((value, index) => { + cy.get('td').eq(index).should('contain.text', value); + }); + }); + + // Check that Basic radio button is checked + cy.findByLabelText('Basic').should('be.checked'); + }); +}; diff --git a/packages/validation/src/vpcs.schema.ts b/packages/validation/src/vpcs.schema.ts index 8a7431bfd7b..3b3ecba58dc 100644 --- a/packages/validation/src/vpcs.schema.ts +++ b/packages/validation/src/vpcs.schema.ts @@ -125,7 +125,8 @@ export const createSubnetSchema = object().shape( { label: labelValidation.required(LABEL_REQUIRED), ipv4: string().when('ipv6', { - is: (value: unknown) => value === '' || value === null || value === undefined, + is: (value: unknown) => + value === '' || value === null || value === undefined, then: string() .required(IP_EITHER_BOTH_NOT_NEITHER) .test({ @@ -163,7 +164,8 @@ export const createSubnetSchema = object().shape( }), }), ipv6: string().when('ipv4', { - is: (value: unknown) => value === '' || value === null || value === undefined, + is: (value: unknown) => + value === '' || value === null || value === undefined, then: string() .required(IP_EITHER_BOTH_NOT_NEITHER) .test({