diff --git a/packages/manager/apps/pci-object-storage/src/lib/iso8601DurationHelper.spec.ts b/packages/manager/apps/pci-object-storage/src/lib/iso8601DurationHelper.spec.ts new file mode 100644 index 000000000000..c5095b6a8b48 --- /dev/null +++ b/packages/manager/apps/pci-object-storage/src/lib/iso8601DurationHelper.spec.ts @@ -0,0 +1,95 @@ +import { describe, it, expect } from 'vitest'; +import { + fromISO8601, + toISO8601, + isValidISO8601, + convertDuration, +} from './iso8601DurationHelper'; + +describe('iso8601DurationHelper', () => { + describe('fromISO8601', () => { + it('parses days', () => { + expect(fromISO8601('P8D')).toEqual({ value: 8, unit: 'D' }); + }); + + it('parses months', () => { + expect(fromISO8601('P6M')).toEqual({ value: 6, unit: 'M' }); + }); + + it('parses years', () => { + expect(fromISO8601('P1Y')).toEqual({ value: 1, unit: 'Y' }); + }); + + // Regression: a 7-day retention is serialized as the ISO 8601 week form + // 'P1W' and previously fell back to the default '1 year' value. + it('normalizes weeks to days', () => { + expect(fromISO8601('P1W')).toEqual({ value: 7, unit: 'D' }); + expect(fromISO8601('P3W')).toEqual({ value: 21, unit: 'D' }); + }); + + it('returns the default value when period is missing', () => { + expect(fromISO8601()).toEqual({ value: 1, unit: 'Y' }); + }); + + it('returns the default value when period is invalid', () => { + expect(fromISO8601('invalid')).toEqual({ value: 1, unit: 'Y' }); + }); + + it('honors a custom default value', () => { + expect(fromISO8601(undefined, { value: 30, unit: 'D' })).toEqual({ + value: 30, + unit: 'D', + }); + }); + }); + + describe('toISO8601', () => { + it('serializes a duration', () => { + expect(toISO8601(7, 'D')).toBe('P7D'); + expect(toISO8601(1, 'Y')).toBe('P1Y'); + }); + + it('throws for non-positive or non-integer values', () => { + expect(() => toISO8601(0, 'D')).toThrow(); + expect(() => toISO8601(-1, 'Y')).toThrow(); + expect(() => toISO8601(1.5, 'M')).toThrow(); + }); + }); + + describe('isValidISO8601', () => { + it('accepts days, weeks, months and years', () => { + expect(isValidISO8601('P7D')).toBe(true); + expect(isValidISO8601('P1W')).toBe(true); + expect(isValidISO8601('P6M')).toBe(true); + expect(isValidISO8601('P1Y')).toBe(true); + }); + + it('rejects invalid input', () => { + expect(isValidISO8601('invalid')).toBe(false); + expect(isValidISO8601('P1H')).toBe(false); + }); + }); + + describe('convertDuration', () => { + it('converts years to days', () => { + expect(convertDuration({ value: 1, unit: 'Y' }, 'D')).toEqual({ + value: 365, + unit: 'D', + }); + }); + + it('converts days to months', () => { + expect(convertDuration({ value: 30, unit: 'D' }, 'M')).toEqual({ + value: 1, + unit: 'M', + }); + }); + + it('returns the same duration when units match', () => { + expect(convertDuration({ value: 7, unit: 'D' }, 'D')).toEqual({ + value: 7, + unit: 'D', + }); + }); + }); +}); diff --git a/packages/manager/apps/pci-object-storage/src/lib/iso8601DurationHelper.ts b/packages/manager/apps/pci-object-storage/src/lib/iso8601DurationHelper.ts index 2ad6a145720b..fa4a9b713bf8 100644 --- a/packages/manager/apps/pci-object-storage/src/lib/iso8601DurationHelper.ts +++ b/packages/manager/apps/pci-object-storage/src/lib/iso8601DurationHelper.ts @@ -13,13 +13,15 @@ export interface Duration { /** * Converts an ISO 8601 duration string to a value and unit object - * Supports Days (D), Months (M), and Years (Y) - * @param period - ISO 8601 duration string (e.g., 'P1Y', 'P30D', 'P6M') + * Supports Days (D), Weeks (W), Months (M), and Years (Y). + * Weeks are normalized to days (1W = 7D) since week is not a display unit. + * @param period - ISO 8601 duration string (e.g., 'P1Y', 'P30D', 'P6M', 'P1W') * @param defaultValue - Default duration to return if parsing fails (defaults to { value: 1, unit: 'Y' }) * @returns Object with value and unit * @example * fromISO8601('P1Y') // { value: 1, unit: 'Y' } * fromISO8601('P30D') // { value: 30, unit: 'D' } + * fromISO8601('P1W') // { value: 7, unit: 'D' } * fromISO8601('invalid') // { value: 1, unit: 'Y' } */ export const fromISO8601 = ( @@ -27,11 +29,18 @@ export const fromISO8601 = ( defaultValue: Duration = { value: 1, unit: 'Y' }, ): Duration => { if (!period) return defaultValue; - const match = period.match(/^P(\d+)([DMY])$/); + const match = period.match(/^P(\d+)([DWMY])$/); if (!match) return defaultValue; + const value = parseInt(match[1], 10); + // ISO 8601 represents an exact number of weeks (e.g. 7 days as 'P1W'). + // Normalize to days since weeks are not a supported display unit. + if (match[2] === 'W') { + return { value: value * 7, unit: 'D' }; + } + const unit = match[2] as DurationUnit; - return { value: parseInt(match[1], 10), unit }; + return { value, unit }; }; /** @@ -58,10 +67,11 @@ export const toISO8601 = (value: number, unit: DurationUnit): string => { * @example * isValidISO8601('P1Y') // true * isValidISO8601('P30D') // true + * isValidISO8601('P1W') // true * isValidISO8601('invalid') // false */ export const isValidISO8601 = (period: string): boolean => { - return /^P(\d+)([DMY])$/.test(period); + return /^P(\d+)([DWMY])$/.test(period); }; /**