diff --git a/packages/api-client/src/controllers/secret.ts b/packages/api-client/src/controllers/secret.ts index 25b6c20f..70a91015 100644 --- a/packages/api-client/src/controllers/secret.ts +++ b/packages/api-client/src/controllers/secret.ts @@ -16,7 +16,7 @@ import { RollBackSecretResponse, UpdateSecretRequest, UpdateSecretResponse -} from '@api-client/types/secret.types' +} from '@keyshade/schema' import { parsePaginationUrl } from '@api-client/core/pagination-parser' export default class SecretController { diff --git a/packages/api-client/src/types/secret.types.d.ts b/packages/api-client/src/types/secret.types.d.ts deleted file mode 100644 index 9383c940..00000000 --- a/packages/api-client/src/types/secret.types.d.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { PageRequest, PageResponse } from '@keyshade/schema' - -export interface Secret { - id: string - name: string - slug: string - createdAt: string - updatedAt: string - rotateAt: string | null - note: string | null - lastUpdatedById: string - projectId: string - project: { - workspaceId: string - } - versions: [ - { - id?: string - environmentId: string - value: string - environment: { - id: string - slug: string - } - } - ] -} - -export interface CreateSecretRequest { - projectSlug: string - name: string - note?: string - rotateAfter?: '24' | '168' | '720' | '8760' | 'never' - entries?: [ - { - value: string - environmentSlug: string - } - ] -} - -export interface CreateSecretResponse extends Secret {} - -export interface UpdateSecretRequest - extends Partial> { - secretSlug: string -} - -export interface UpdateSecretResponse { - secret: Pick - updatedVersions: [ - { - id?: string - environmentId: string - environment: { - id: string - slug: string - } - value: string - } - ] -} - -export interface DeleteSecretRequest { - secretSlug: string -} - -export interface DeleteSecretResponse {} - -export interface RollBackSecretRequest { - environmentSlug: string - version: number - secretSlug: string -} -export interface RollBackSecretResponse { - count: string -} - -export interface GetAllSecretsOfProjectRequest extends PageRequest { - projectSlug: string -} -export interface GetAllSecretsOfProjectResponse - extends PageResponse<{ - secret: Omit & { - lastUpdatedBy: { - id: string - name: string - } - } - values: { - environment: { - id: string - name: string - slug: string - } - value: string - version: number - } - }> {} - -export interface GetAllSecretsOfEnvironmentRequest { - projectSlug: string - environmentSlug: string -} -export type GetAllSecretsOfEnvironmentResponse = { - name: string - value: string - isPlaintext: boolean -}[] - -export interface GetRevisionsOfSecretRequest extends Partial { - secretSlug: string - environmentSlug: string -} - -export interface GetRevisionsOfSecretResponse - extends PageResponse<{ - id: string - value: string - version: number - createdOn: string - createdById: string - environmentId: string - }> {} diff --git a/packages/schema/src/index.types.ts b/packages/schema/src/index.types.ts index 3c683178..81b824ba 100644 --- a/packages/schema/src/index.types.ts +++ b/packages/schema/src/index.types.ts @@ -1,7 +1,6 @@ import { z } from 'zod' import { CreateApiKeySchema, UpdateApiKeySchema } from './api-key' import { CreateIntegrationSchema, UpdateIntegrationSchema } from './integration' -import { CreateSecretSchema, UpdateSecretSchema } from './secret' import { CreateVariableSchema, UpdateVariableSchema } from './variable' import { CreateWorkspaceRoleSchema, @@ -24,8 +23,7 @@ export type TUpdateIntegration = z.infer export * from './project/index.types' -export type TCreateSecret = z.infer -export type TUpdateSecret = z.infer +export * from './secret/index.types' export * from './user/index.types' diff --git a/packages/schema/src/secret.ts b/packages/schema/src/secret.ts deleted file mode 100644 index 4c15dfdc..00000000 --- a/packages/schema/src/secret.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { z } from 'zod' -import { rotateAfterEnum } from '@/enums' - -export const CreateSecretSchema = z.object({ - name: z.string(), - note: z.string().optional(), - rotateAfter: rotateAfterEnum.optional(), - entries: z.array( - z.object({ - environmentId: z.string(), - value: z.string() - }) - ) -}) - -export const UpdateSecretSchema = CreateSecretSchema.partial() diff --git a/packages/schema/src/secret/index.ts b/packages/schema/src/secret/index.ts new file mode 100644 index 00000000..a3196906 --- /dev/null +++ b/packages/schema/src/secret/index.ts @@ -0,0 +1,141 @@ +import { z } from 'zod' +import { PageRequestSchema, PageResponseSchema } from '@/pagination/pagination' +import { rotateAfterEnum } from '@/enums' + +export const SecretSchema = z.object({ + id: z.string(), + name: z.string(), + slug: z.string(), + createdAt: z.string(), + updatedAt: z.string(), + rotateAt: z.string().nullable(), + note: z.string().nullable(), + lastUpdatedById: z.string(), + projectId: z.string(), + project: z.object({ + workspaceId: z.string() + }), + versions: z.array( + z.object({ + id: z.string().optional(), + environmentId: z.string(), + value: z.string(), + environment: z.object({ + id: z.string(), + slug: z.string() + }) + }) + ) +}) + +export const CreateSecretRequestSchema = z.object({ + projectSlug: z.string(), + name: z.string(), + note: z.string().optional(), + rotateAfter: rotateAfterEnum.optional(), + entries: z + .array( + z.object({ + value: z.string(), + environmentSlug: z.string() + }) + ) + .optional() +}) + +export const CreateSecretResponseSchema = SecretSchema + +export const UpdateSecretRequestSchema = + CreateSecretRequestSchema.partial().extend({ + secretSlug: z.string() + }) + +export const UpdateSecretResponseSchema = z.object({ + secret: z.object({ + id: z.string(), + name: z.string(), + slug: z.string(), + note: z.string().nullable() + }), + updatedVersions: z.array( + z.object({ + id: z.string().optional(), + environmentId: z.string(), + environment: z.object({ + id: z.string(), + slug: z.string() + }), + value: z.string() + }) + ) +}) + +export const DeleteSecretRequestSchema = z.object({ + secretSlug: z.string() +}) + +export const DeleteSecretResponseSchema = z.void() + +export const RollBackSecretRequestSchema = z.object({ + environmentSlug: z.string(), + version: z.number(), + secretSlug: z.string() +}) + +export const RollBackSecretResponseSchema = z.object({ + count: z.string() +}) + +export const GetAllSecretsOfProjectRequestSchema = PageRequestSchema.extend({ + projectSlug: z.string() +}) + +export const GetAllSecretsOfProjectResponseSchema = PageResponseSchema( + z.object({ + secret: SecretSchema.omit({ versions: true, project: true }).extend({ + lastUpdatedBy: z.object({ + id: z.string(), + name: z.string() + }) + }), + values: z.object({ + environment: z.object({ + id: z.string(), + name: z.string(), + slug: z.string() + }), + value: z.string(), + version: z.number() + }) + }) +) + +export const GetAllSecretsOfEnvironmentRequestSchema = z.object({ + projectSlug: z.string(), + environmentSlug: z.string() +}) + +export const GetAllSecretsOfEnvironmentResponseSchema = z.array( + z.object({ + name: z.string(), + value: z.string(), + isPlaintext: z.boolean() + }) +) + +export const GetRevisionsOfSecretRequestSchema = + PageRequestSchema.partial().extend({ + secretSlug: z.string(), + environmentSlug: z.string() + }) + +export const GetRevisionsOfSecretResponseSchema = PageResponseSchema( + z.object({ + id: z.string(), + value: z.string(), + version: z.number(), + createdOn: z.string(), + createdById: z.string(), + environmentId: z.string() + }) +) diff --git a/packages/schema/src/secret/index.types.ts b/packages/schema/src/secret/index.types.ts new file mode 100644 index 00000000..70fb2fa9 --- /dev/null +++ b/packages/schema/src/secret/index.types.ts @@ -0,0 +1,62 @@ +import { z } from 'zod' +import { + SecretSchema, + CreateSecretRequestSchema, + CreateSecretResponseSchema, + UpdateSecretRequestSchema, + UpdateSecretResponseSchema, + DeleteSecretRequestSchema, + DeleteSecretResponseSchema, + RollBackSecretRequestSchema, + RollBackSecretResponseSchema, + GetAllSecretsOfProjectRequestSchema, + GetAllSecretsOfProjectResponseSchema, + GetAllSecretsOfEnvironmentRequestSchema, + GetAllSecretsOfEnvironmentResponseSchema, + GetRevisionsOfSecretRequestSchema, + GetRevisionsOfSecretResponseSchema +} from '.' + +export type Secret = z.infer + +export type CreateSecretRequest = z.infer + +export type CreateSecretResponse = z.infer + +export type UpdateSecretRequest = z.infer + +export type UpdateSecretResponse = z.infer + +export type DeleteSecretRequest = z.infer + +export type DeleteSecretResponse = z.infer + +export type RollBackSecretRequest = z.infer + +export type RollBackSecretResponse = z.infer< + typeof RollBackSecretResponseSchema +> + +export type GetAllSecretsOfProjectRequest = z.infer< + typeof GetAllSecretsOfProjectRequestSchema +> + +export type GetAllSecretsOfProjectResponse = z.infer< + typeof GetAllSecretsOfProjectResponseSchema +> + +export type GetAllSecretsOfEnvironmentRequest = z.infer< + typeof GetAllSecretsOfEnvironmentRequestSchema +> + +export type GetAllSecretsOfEnvironmentResponse = z.infer< + typeof GetAllSecretsOfEnvironmentResponseSchema +> + +export type GetRevisionsOfSecretRequest = z.infer< + typeof GetRevisionsOfSecretRequestSchema +> + +export type GetRevisionsOfSecretResponse = z.infer< + typeof GetRevisionsOfSecretResponseSchema +> diff --git a/packages/schema/tests/secret.spec.ts b/packages/schema/tests/secret.spec.ts index e0652523..07e4b18e 100644 --- a/packages/schema/tests/secret.spec.ts +++ b/packages/schema/tests/secret.spec.ts @@ -1,51 +1,519 @@ -import { CreateSecretSchema } from '@/secret' +import { + SecretSchema, + CreateSecretRequestSchema, + CreateSecretResponseSchema, + UpdateSecretRequestSchema, + UpdateSecretResponseSchema, + DeleteSecretRequestSchema, + DeleteSecretResponseSchema, + RollBackSecretRequestSchema, + RollBackSecretResponseSchema, + GetAllSecretsOfProjectRequestSchema, + GetAllSecretsOfProjectResponseSchema, + GetAllSecretsOfEnvironmentRequestSchema, + GetAllSecretsOfEnvironmentResponseSchema, + GetRevisionsOfSecretRequestSchema, + GetRevisionsOfSecretResponseSchema +} from '@/secret' import { rotateAfterEnum } from '@/enums' describe('Secret Schema Tests', () => { - it('should validate if proper input is specified for CreateSecretSchema', () => { - const result = CreateSecretSchema.safeParse({ - name: 'Secret Test', - rotateAfter: rotateAfterEnum.enum['720'], - entries: [{ environmentId: 'env123', value: 'secret-value' }] + // Tests for SecretSchema + it('should validate a valid SecretSchema', () => { + const result = SecretSchema.safeParse({ + id: 'secret123', + name: 'Secret Name', + slug: 'secret-slug', + createdAt: '2023-10-01T00:00:00Z', + updatedAt: '2023-10-01T00:00:00Z', + rotateAt: null, + note: 'This is a note', + lastUpdatedById: 'user123', + projectId: 'project123', + project: { + workspaceId: 'workspace123' + }, + versions: [ + { + id: 'version123', + environmentId: 'env123', + value: 'secret-value', + environment: { + id: 'env123', + slug: 'development' + } + } + ] }) + expect(result.success).toBe(true) + }) + + it('should not validate an invalid SecretSchema', () => { + const result = SecretSchema.safeParse({ + id: 'secret123', + name: 'Secret Name', + slug: 'secret-slug', + createdAt: '2023-10-01T00:00:00Z', + updatedAt: '2023-10-01T00:00:00Z', + rotateAt: null, + note: 'This is a note', + lastUpdatedById: 'user123', + projectId: 'project123', + project: { + workspaceId: 'workspace123' + }, + versions: [ + { + id: 'version123', + environmentId: 'env123', + value: 'secret-value', + environment: { + id: 'env123' + // Missing slug + } + } + ] + }) + expect(result.success).toBe(false) + expect(result.error?.issues).toHaveLength(1) + }) + // Tests for CreateSecretRequestSchema + it('should validate a valid CreateSecretRequestSchema', () => { + const result = CreateSecretRequestSchema.safeParse({ + projectSlug: 'project-slug', + name: 'Secret Name', + note: 'This is a note', + rotateAfter: rotateAfterEnum.enum['24'], + entries: [ + { + value: 'secret-value', + environmentSlug: 'development' + } + ] + }) expect(result.success).toBe(true) }) - it('should validate if only required fields are specified for CreateSecretSchema', () => { - const result = CreateSecretSchema.safeParse({ - name: 'Secret Test', - entries: [{ environmentId: 'env123', value: 'secret-value' }] + it('should not validate an invalid CreateSecretRequestSchema', () => { + const result = CreateSecretRequestSchema.safeParse({ + projectSlug: 'project-slug', + name: 'Secret Name', + note: 'This is a note', + rotateAfter: '30', // Invalid rotateAfter value + entries: [ + { + value: 'secret-value', + environmentSlug: 'development' + } + ] }) + expect(result.success).toBe(false) + expect(result.error?.issues).toHaveLength(1) + }) + // Tests for CreateSecretResponseSchema + it('should validate a valid CreateSecretResponseSchema', () => { + const result = CreateSecretResponseSchema.safeParse({ + id: 'secret123', + name: 'Secret Name', + slug: 'secret-slug', + createdAt: '2023-10-01T00:00:00Z', + updatedAt: '2023-10-01T00:00:00Z', + rotateAt: null, + note: 'This is a note', + lastUpdatedById: 'user123', + projectId: 'project123', + project: { + workspaceId: 'workspace123' + }, + versions: [ + { + id: 'version123', + environmentId: 'env123', + value: 'secret-value', + environment: { + id: 'env123', + slug: 'development' + } + } + ] + }) expect(result.success).toBe(true) }) - it('should not validate if required fields are missing for CreateSecretSchema', () => { - const result = CreateSecretSchema.safeParse({ - rotateAfter: rotateAfterEnum.enum['720'] + it('should not validate an invalid CreateSecretResponseSchema', () => { + const result = CreateSecretResponseSchema.safeParse({ + id: 'secret123', + name: 'Secret Name', + slug: 'secret-slug', + createdAt: '2023-10-01T00:00:00Z', + updatedAt: '2023-10-01T00:00:00Z', + rotateAt: null, + note: 'This is a note', + lastUpdatedById: 'user123', + projectId: 'project123', + project: { + workspaceId: 'workspace123' + }, + versions: [ + { + id: 'version123', + environmentId: 'env123', + value: 'secret-value', + environment: { + id: 'env123' + // Missing slug + } + } + ] + }) + expect(result.success).toBe(false) + expect(result.error?.issues).toHaveLength(1) + }) + + // Tests for UpdateSecretRequestSchema + it('should validate a valid UpdateSecretRequestSchema', () => { + const result = UpdateSecretRequestSchema.safeParse({ + secretSlug: 'secret-slug', + name: 'Updated Secret Name', + note: 'Updated note' }) + expect(result.success).toBe(true) + }) + it('should not validate an invalid UpdateSecretRequestSchema', () => { + const result = UpdateSecretRequestSchema.safeParse({ + secretSlug: 123, // Should be a string + name: 'Updated Secret Name', + note: 'Updated note' + }) + expect(result.success).toBe(false) + expect(result.error?.issues).toHaveLength(1) + }) + + // Tests for UpdateSecretResponseSchema + it('should validate a valid UpdateSecretResponseSchema', () => { + const result = UpdateSecretResponseSchema.safeParse({ + secret: { + id: 'secret123', + name: 'Secret Name', + slug: 'secret-slug', + note: 'This is a note' + }, + updatedVersions: [ + { + id: 'version123', + environmentId: 'env123', + environment: { + id: 'env123', + slug: 'development' + }, + value: 'secret-value' + } + ] + }) + expect(result.success).toBe(true) + }) + + it('should not validate an invalid UpdateSecretResponseSchema', () => { + const result = UpdateSecretResponseSchema.safeParse({ + secret: { + id: 'secret123', + name: 'Secret Name', + slug: 'secret-slug', + note: 'This is a note' + }, + updatedVersions: [ + { + id: 'version123', + environmentId: 'env123', + environment: { + id: 'env123' + // Missing slug + }, + value: 'secret-value' + } + ] + }) + expect(result.success).toBe(false) + expect(result.error?.issues).toHaveLength(1) + }) + + // Tests for DeleteSecretRequestSchema + it('should validate a valid DeleteSecretRequestSchema', () => { + const result = DeleteSecretRequestSchema.safeParse({ + secretSlug: 'secret-slug' + }) + expect(result.success).toBe(true) + }) + + it('should not validate an invalid DeleteSecretRequestSchema', () => { + const result = DeleteSecretRequestSchema.safeParse({ + secretSlug: 123 // Should be a string + }) + expect(result.success).toBe(false) + expect(result.error?.issues).toHaveLength(1) + }) + + // Tests for DeleteSecretResponseSchema + it('should validate a valid DeleteSecretResponseSchema', () => { + const result = DeleteSecretResponseSchema.safeParse(undefined) + expect(result.success).toBe(true) + }) + + it('should not validate an invalid DeleteSecretResponseSchema', () => { + const result = DeleteSecretResponseSchema.safeParse({ + unexpectedField: 'value' + }) expect(result.success).toBe(false) - expect(result.error?.issues).toHaveLength(2) }) - it('should not validate if invalid types are specified for CreateSecretSchema', () => { - const result = CreateSecretSchema.safeParse({ - name: 123, - entries: [{ environmentId: 'env123', value: 456 }] + // Tests for RollBackSecretRequestSchema + it('should validate a valid RollBackSecretRequestSchema', () => { + const result = RollBackSecretRequestSchema.safeParse({ + environmentSlug: 'development', + version: 1, + secretSlug: 'secret-slug' }) + expect(result.success).toBe(true) + }) + it('should not validate an invalid RollBackSecretRequestSchema', () => { + const result = RollBackSecretRequestSchema.safeParse({ + environmentSlug: 'development', + version: 'one', // Should be a number + secretSlug: 'secret-slug' + }) + expect(result.success).toBe(false) + expect(result.error?.issues).toHaveLength(1) + }) + + // Tests for RollBackSecretResponseSchema + it('should validate a valid RollBackSecretResponseSchema', () => { + const result = RollBackSecretResponseSchema.safeParse({ + count: '1' + }) + expect(result.success).toBe(true) + }) + + it('should not validate an invalid RollBackSecretResponseSchema', () => { + const result = RollBackSecretResponseSchema.safeParse({ + count: 1 // Should be a string + }) + expect(result.success).toBe(false) + expect(result.error?.issues).toHaveLength(1) + }) + + // Tests for GetAllSecretsOfProjectRequestSchema + it('should validate a valid GetAllSecretsOfProjectRequestSchema', () => { + const result = GetAllSecretsOfProjectRequestSchema.safeParse({ + projectSlug: 'project-slug', + page: 1, + limit: 10 + }) + expect(result.success).toBe(true) + }) + + it('should not validate an invalid GetAllSecretsOfProjectRequestSchema', () => { + const result = GetAllSecretsOfProjectRequestSchema.safeParse({ + projectSlug: 123, // Should be a string + page: 1, + limit: 10 + }) + expect(result.success).toBe(false) + expect(result.error?.issues).toHaveLength(1) + }) + + // Tests for GetAllSecretsOfProjectResponseSchema + it('should validate a valid GetAllSecretsOfProjectResponseSchema', () => { + const result = GetAllSecretsOfProjectResponseSchema.safeParse({ + items: [ + { + secret: { + id: 'secret123', + name: 'Secret Name', + slug: 'secret-slug', + createdAt: '2023-10-01T00:00:00Z', + updatedAt: '2023-10-01T00:00:00Z', + rotateAt: null, + note: 'This is a note', + lastUpdatedById: 'user123', + projectId: 'project123', + lastUpdatedBy: { + id: 'user123', + name: 'John Doe' + } + }, + values: { + environment: { + id: 'env123', + name: 'Development', + slug: 'development' + }, + value: 'secret-value', + version: 1 + } + } + ], + metadata: { + page: 1, + perPage: 10, + pageCount: 1, + totalCount: 1, + links: { + self: 'http://example.com/page/1', + first: 'http://example.com/page/1', + previous: null, + next: null, + last: 'http://example.com/page/1' + } + } + }) + expect(result.success).toBe(true) + }) + + it('should not validate an invalid GetAllSecretsOfProjectResponseSchema', () => { + const result = GetAllSecretsOfProjectResponseSchema.safeParse({ + items: 'not-an-array', // Should be an array + metadata: { + page: 1, + perPage: 10, + pageCount: 1, + totalCount: 1, + links: { + self: 'http://example.com/page/1', + first: 'http://example.com/page/1', + previous: null, + next: null, + last: 'http://example.com/page/1' + } + } + }) + expect(result.success).toBe(false) + expect(result.error?.issues).toHaveLength(1) + }) + + // Tests for GetAllSecretsOfEnvironmentRequestSchema + it('should validate a valid GetAllSecretsOfEnvironmentRequestSchema', () => { + const result = GetAllSecretsOfEnvironmentRequestSchema.safeParse({ + projectSlug: 'project-slug', + environmentSlug: 'development' + }) + expect(result.success).toBe(true) + }) + + it('should not validate an invalid GetAllSecretsOfEnvironmentRequestSchema', () => { + const result = GetAllSecretsOfEnvironmentRequestSchema.safeParse({ + projectSlug: 123, // Should be a string + environmentSlug: 'development' + }) + expect(result.success).toBe(false) + expect(result.error?.issues).toHaveLength(1) + }) + + // Tests for GetAllSecretsOfEnvironmentResponseSchema + it('should validate a valid GetAllSecretsOfEnvironmentResponseSchema', () => { + const result = GetAllSecretsOfEnvironmentResponseSchema.safeParse([ + { + name: 'SECRET_NAME', + value: 'SECRET_VALUE', + isPlaintext: true + } + ]) + expect(result.success).toBe(true) + }) + + it('should not validate an invalid GetAllSecretsOfEnvironmentResponseSchema', () => { + const result = GetAllSecretsOfEnvironmentResponseSchema.safeParse([ + { + name: 'SECRET_NAME', + value: 123, // Should be a string + isPlaintext: 'true' // Should be a boolean + } + ]) expect(result.success).toBe(false) expect(result.error?.issues).toHaveLength(2) }) - it('should validate if optional fields are omitted for CreateSecretSchema', () => { - const result = CreateSecretSchema.safeParse({ - name: 'Secret Test', - entries: [{ environmentId: 'env123', value: 'secret-value' }] + // Tests for GetRevisionsOfSecretRequestSchema + it('should validate a valid GetRevisionsOfSecretRequestSchema', () => { + const result = GetRevisionsOfSecretRequestSchema.safeParse({ + secretSlug: 'my-secret', + environmentSlug: 'development', + page: 1, + limit: 10 }) + expect(result.success).toBe(true) + }) + it('should not validate an invalid GetRevisionsOfSecretRequestSchema', () => { + const result = GetRevisionsOfSecretRequestSchema.safeParse({ + secretSlug: 'my-secret', + environmentSlug: 123, // Should be a string + page: 'one' // Should be a number + }) + expect(result.success).toBe(false) + expect(result.error?.issues).toHaveLength(2) + }) + + // Tests for GetRevisionsOfSecretResponseSchema + it('should validate a valid GetRevisionsOfSecretResponseSchema', () => { + const result = GetRevisionsOfSecretResponseSchema.safeParse({ + items: [ + { + id: 'revision123', + value: 'secret-value', + version: 1, + createdOn: '2023-10-01T00:00:00Z', + createdById: 'user123', + environmentId: 'env123' + } + ], + metadata: { + page: 1, + perPage: 10, + pageCount: 1, + totalCount: 1, + links: { + self: 'http://example.com/page/1', + first: 'http://example.com/page/1', + previous: null, + next: null, + last: 'http://example.com/page/1' + } + } + }) expect(result.success).toBe(true) }) + + it('should not validate an invalid GetRevisionsOfSecretResponseSchema', () => { + const result = GetRevisionsOfSecretResponseSchema.safeParse({ + items: [ + { + id: 'revision123', + value: 'secret-value', + version: 'one', // Should be a number + createdOn: '2023-10-01T00:00:00Z', + createdById: 'user123', + environmentId: 'env123' + } + ], + metadata: { + page: 1, + perPage: 'ten', // Should be a number + pageCount: 1, + totalCount: 1, + links: { + self: 'http://example.com/page/1', + first: 'http://example.com/page/1', + previous: null, + next: null, + last: 'http://example.com/page/1' + } + } + }) + expect(result.success).toBe(false) + expect(result.error?.issues).toHaveLength(2) + }) })