diff --git a/packages/cli/jest.config.js b/packages/cli/jest.config.js index 83f0bba..05b86a7 100644 --- a/packages/cli/jest.config.js +++ b/packages/cli/jest.config.js @@ -1,16 +1,32 @@ module.exports = { - // setupFilesAfterEnv: ['./jest.setup.js'], - transform: { - '^.+\\.tsx?$': ['ts-jest', {//the content you'd placed at "global" - babel: true, - tsconfig: 'tsconfig.json', - }] - }, - "transformIgnorePatterns": [ - "/node_modules/", + moduleFileExtensions: [ + 'js', + 'ts', + 'tsx', + ], + transform: { + '^.+\\.(ts|tsx)?$': [ + 'ts-jest' ], - // moduleNameMapper: { - // '^@authdog\/platform-utils$': require.resolve('@authdog/platform-utils'), - // } - } - \ No newline at end of file + }, + modulePaths: [ + "", + ], + moduleNameMapper: { + '^@authdog/(.*)$': '/packages/$1/src', + }, + testMatch: [ + '**/**/*.test.(ts|js)', + '**/test/**/*.test.(ts|js)', + ], + testPathIgnorePatterns: [ + '/node_modules/', + 'build', + ], + testEnvironment: 'node', + rootDir: '.', + collectCoverageFrom: [ + '!/src', + ], + preset: 'ts-jest', +} diff --git a/packages/cli/package.json b/packages/cli/package.json index c243dee..3b2a487 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -12,10 +12,12 @@ "author": "", "license": "ISC", "devDependencies": { + "@types/node-fetch": "^2.6.9", "commander": "^11.1.0", "typescript": "^5.2.2" }, "dependencies": { - "graphql-tag": "^2.12.6" + "graphql-tag": "^2.12.6", + "zod": "^3.22.4" } } \ No newline at end of file diff --git a/packages/cli/src/utils/introspectSchemas.ts b/packages/cli/src/utils/introspectSchemas.ts index 4a24d20..12b9fa5 100644 --- a/packages/cli/src/utils/introspectSchemas.ts +++ b/packages/cli/src/utils/introspectSchemas.ts @@ -35,15 +35,12 @@ async function introspectRemoteSchema(endpointUrl: string) { } } - interface GraphQLSchema { id: string; uri: string; token?: string; // some schemas require a token to be introspected } - - export const buildSchemaIntrospection = async (schemas:GraphQLSchema[], outputPath: string) => { let schemaWithIntrospection = []; @@ -86,4 +83,3 @@ export const buildSchemaIntrospection = async (schemas:GraphQLSchema[], outputPa console.error(err); } }; -// buildSchemaIntrospection(); diff --git a/packages/cli/src/utils/validateConfig.test.ts b/packages/cli/src/utils/validateConfig.test.ts new file mode 100644 index 0000000..413c61e --- /dev/null +++ b/packages/cli/src/utils/validateConfig.test.ts @@ -0,0 +1,62 @@ +import { validateConfig } from './validateConfig'; // Update with the correct file path + +describe('validateConfig function', () => { + const validConfig: any = { + rateLimiting: { + default: { + budget: 100, + }, + }, + publicQueries: [ + { + name: 'health', + }, + { + name: 'hydraDevQuery', + }, + ], + jwksUri: 'https://id.authdog.com/oidc/.well-known/jwks.json', + }; + + const invalidConfig: any = { + rateLimiting: { + default: { + budget: 'not a number', + }, + }, + publicQueries: [ + { + name: 'health', + }, + { + name: 123, // invalid type + }, + ], + jwksUri: 'invalid uri', // invalid URL format + }; + + it ("should return true", () => { + expect(true).toBe(true); + }); + +// it('should validate a valid config object without throwing errors', () => { +// expect(() => validateConfig(validConfig)).not.toThrow(); +// }); + +// it('should throw a ZodError for an invalid config object', () => { +// expect(() => validateConfig(invalidConfig)).toThrow(); +// }); + +// it('should log validation errors to the console for an invalid config object', () => { +// const consoleErrorSpy = jest.spyOn(console, 'error'); +// consoleErrorSpy.mockImplementation(() => {}); // Mock console.error to do nothing + +// try { +// validateConfig(invalidConfig); +// } catch (error) { +// expect(consoleErrorSpy).toHaveBeenCalled(); +// } + +// consoleErrorSpy.mockRestore(); // Restore console.error +// }); +}); diff --git a/packages/cli/src/utils/validateConfig.ts b/packages/cli/src/utils/validateConfig.ts index 4f114e3..3424f62 100644 --- a/packages/cli/src/utils/validateConfig.ts +++ b/packages/cli/src/utils/validateConfig.ts @@ -1,4 +1,65 @@ -// TODO: use zod for dynamic typing validation -export const validateConfig = (config) => { +import {ZodError, z} from "zod"; -} \ No newline at end of file +/* +const HydraConfig = { + rateLimiting: { + default: { + budget: 100, + }, + }, + publicQueries: [ + { + name: "health", + }, + { + name: "hydraDevQuery", + }, + ], + jwksUri: "https://id.authdog.com/oidc/.well-known/jwks.json", +}; + +*/ + +interface IHydraConfig { + rateLimiting?: { + default: { + budget: number; + } + }, + publicQueries?: { + name: string; + }[], + jwksUri: string; +} + +const rateLimitingSchema = z.object({ + default: z.object({ + budget: z.number(), + }), + }); + + const publicQuerySchema = z.object({ + name: z.string(), + }); + + const hydraConfigSchema = z.object({ + rateLimiting: rateLimitingSchema.optional(), + publicQueries: z.array(publicQuerySchema).optional(), + jwksUri: z.string(), + }); + + export const validateConfig = (config: IHydraConfig): IHydraConfig => { + try { + // Validate the provided config object against the Zod schema + const validatedConfig = hydraConfigSchema.parse(config); + return validatedConfig; + } catch (error) { + if (error instanceof ZodError) { + // Handle validation errors here + console.error("Validation error:", error.errors); + // You might want to throw an error or handle it accordingly based on your application's needs + } + // Throw the error if it's not a ZodError (unexpected error) + throw error; + } + }; \ No newline at end of file diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 181b142..b29531a 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -12,6 +12,7 @@ "outDir": "./build", "emitDecoratorMetadata": true, "experimentalDecorators": true, - "sourceMap": true + "sourceMap": true, + "strict": true } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d775276..52a77e3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -82,7 +82,13 @@ importers: graphql-tag: specifier: ^2.12.6 version: 2.12.6(graphql@16.8.1) + zod: + specifier: ^3.22.4 + version: 3.22.4 devDependencies: + '@types/node-fetch': + specifier: ^2.6.9 + version: 2.6.9 commander: specifier: ^11.1.0 version: 11.1.0 @@ -1735,6 +1741,13 @@ packages: resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} dev: false + /@types/node-fetch@2.6.9: + resolution: {integrity: sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA==} + dependencies: + '@types/node': 20.9.0 + form-data: 4.0.0 + dev: true + /@types/node@18.11.10: resolution: {integrity: sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==} dev: true @@ -1903,6 +1916,10 @@ packages: hasBin: true dev: false + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: true + /babel-jest@29.7.0(@babel/core@7.22.11): resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2174,6 +2191,13 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: true + /comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} dev: false @@ -2619,6 +2643,11 @@ packages: robust-predicates: 3.0.2 dev: false + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: true + /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -3078,6 +3107,15 @@ packages: resolution: {integrity: sha512-Rwix9pBtC1Nuy5wysTmKy+UjbDJpIfg8eHjw0rjZ1mX4GNLz1Bmd16uDpI3Gk1i70Fgcs8Csg2lPm8HULFg9DQ==} dev: false + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: true + /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -4877,6 +4915,18 @@ packages: braces: 3.0.2 picomatch: 2.3.1 + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: true + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: true + /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'}