Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/integration tests #6

Merged
merged 8 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Run Integration Tests

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
integration-tests:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
services:
registry:
image: registry:2
ports:
- 5000:5000
steps:
- name: Check out the repo
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: network=host

- name: Copy patches for Docker Buildx
run: cp -r patches/* packages/graphql-mesh/patches

- name: Build and push on local registry
id: docker_build
uses: docker/build-push-action@v5
with:
context: ./packages/graphql-mesh
push: true
tags: localhost:5000/test/graphql-mesh:latest
platforms: linux/amd64

- name: Setup services for testing purpose
run: export IMAGE_TAG=localhost:5000/test/graphql-mesh:latest && cd ./test/integration && docker compose up -d

- name: Wait for services to be ready
run: sleep 30

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}

- name: Install dependencies
run: cd ./test/integration/tests && npm install

- name: Run tests
run: cd ./test/integration/tests && npm test
2 changes: 0 additions & 2 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
node-version: ${{ matrix.node-version }}

- name: Set up Node.js
uses: actions/setup-node@v3
Expand Down
12 changes: 12 additions & 0 deletions packages/directive-headers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,15 @@ export default class HeadersDirectiveTransform implements MeshTransform {
})
}
}

export const headersDirectiveTypeDef: string = /* GraphQL */ `
input Header {
key: String
value: String
}

"""
This directive is used to add headers to the request.
"""
directive @headers(input: [Header]) on FIELD
`
7 changes: 7 additions & 0 deletions packages/directive-no-auth/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,10 @@ export default class NoAuthDirectiveTransform implements MeshTransform {
})
}
}

export const noAuthDirectiveTypeDef: string = /* GraphQL */ `
"""
This directive is used to disable the authorization header for the request
"""
directive @noAuth on FIELD
`
1 change: 0 additions & 1 deletion packages/directive-spl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"build:cjs": "tsc --project tsconfig-cjs.json",
"build:esm": "tsc --project tsconfig-esm.json",
"build": "rm -rf _build && npm run build:esm && npm run build:cjs && node ./scripts/prepare-package-json",
"dev": "vite",
"pack": "npm run build && npm pack --pack-destination ../graphql-mesh/local-pkg",
"test": "vitest"
},
Expand Down
7 changes: 7 additions & 0 deletions packages/directive-spl/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,10 @@ export default class SplDirectiveTransform implements MeshTransform {
})
}
}

export const splDirectiveTypeDef: string = /* GraphQL */ `
"""
This is a very small, lightweight, straightforward and non-evaluated expression language to sort, filter and paginate arrays of maps.
"""
directive @SPL(query: String) on FIELD
`
14 changes: 1 addition & 13 deletions packages/directive-spl/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,4 @@
/// <reference types="vitest" />
// Configure Vitest (https://vitest.dev/config/)
import { resolve } from 'path'
import { defineConfig } from 'vite'
import dts from 'vite-plugin-dts'
// https://vitejs.dev/guide/build.html#library-mode
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'my-lib',
fileName: 'my-lib'
}
},
plugins: [dts()]
})
export default defineConfig({})
15 changes: 12 additions & 3 deletions packages/graphql-mesh/.meshrc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { YamlConfig } from '@graphql-mesh/types'
import ConfigFromSwaggers from './utils/ConfigFromSwaggers'
import { splDirectiveTypeDef } from 'directive-spl'
import { headersDirectiveTypeDef } from 'directive-headers'
import { noAuthDirectiveTypeDef } from 'directive-no-auth'

const configFromSwaggers = new ConfigFromSwaggers()
const { defaultConfig, additionalTypeDefs, sources } =
Expand All @@ -17,13 +20,19 @@ const config = <YamlConfig.Config>{
}
},
...(defaultConfig.transforms || [])
],
].filter(Boolean),
sources: [...sources],
additionalTypeDefs: [defaultConfig.additionalTypeDefs || '', ...additionalTypeDefs],
additionalTypeDefs: [
splDirectiveTypeDef,
headersDirectiveTypeDef,
noAuthDirectiveTypeDef,
additionalTypeDefs,
defaultConfig.additionalTypeDefs || ''
].filter(Boolean),
additionalResolvers: [
...(defaultConfig.additionalResolvers || []),
'./utils/additionalResolvers.ts'
]
].filter(Boolean)
}

export default config
1 change: 1 addition & 0 deletions packages/graphql-mesh/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ VOLUME /app/sources
VOLUME /app/config.yaml
VOLUME /app/transforms
VOLUME /app/plugins
VOLUME /app/resolvers

CMD [ "npm", "run", "serve" ]
Binary file modified packages/graphql-mesh/local-pkg/directive-headers-1.0.0.tgz
Binary file not shown.
Binary file modified packages/graphql-mesh/local-pkg/directive-no-auth-1.0.0.tgz
Binary file not shown.
Binary file modified packages/graphql-mesh/local-pkg/directive-spl-1.0.0.tgz
Binary file not shown.
Binary file not shown.
24 changes: 19 additions & 5 deletions packages/graphql-mesh/scripts/download-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,34 @@ let config = getConfig()
const sources = config?.sources?.filter((source) => source?.handler?.openapi) || []
const swaggers = sources.map((source) => source?.handler?.openapi?.source) || []

/**
* Get the name of generated from the source object
* @param {Record<string, unknown>} source
* @returns {string | undefined}
*/
const getFileName = (url: string): string | undefined => {
return sources.find((source) => source?.handler?.openapi?.source === url)?.name
}

/**
* Download the swagger from the given URL and save it to the sources folder
* @param {string} url
*/
const downSwaggerFromUrl = async (url: string): Promise<void> => {
const downSwaggerFromUrl = async (url: string | undefined, index: string): Promise<void> => {
if (!url) return Promise.resolve()
try {
const content: Record<string, unknown> = await readFileOrUrl(url, {
allowUnknownExtensions: true,
cwd: '.',
fetch: fetch,
importFn: null,
importFn: (mod) => import(mod),
logger: logger
})
const fileName = url.split('/').pop()
let fileName = getFileName(url) || `${index}-${url.split('/').pop()}`
if (!fileName.endsWith('.json')) {
fileName += '.json'
}

if (fileName) {
const filePath = `./sources/${fileName}`
writeFileSync(filePath, JSON.stringify(content, null, 2), 'utf8')
Expand All @@ -37,7 +51,7 @@ const downSwaggerFromUrl = async (url: string): Promise<void> => {
* Download all the swaggers from the given URLs
* @param {string[]} swaggers
*/
const downloadSwaggers = (swaggers: string[]) => {
const downloadSwaggers = (swaggers: (string | undefined)[]) => {
logger.info(`Downloading ${swaggers.length} swaggers sources...`)

// Create the sources folder if it doesn't exist
Expand All @@ -46,7 +60,7 @@ const downloadSwaggers = (swaggers: string[]) => {
}

if (swaggers.length) {
swaggers.forEach(downSwaggerFromUrl)
swaggers.forEach((file, index) => downSwaggerFromUrl(file, index.toString()))
}
}

Expand Down
38 changes: 21 additions & 17 deletions packages/graphql-mesh/utils/ConfigFromSwaggers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { globSync } from 'glob'
import { readFileSync } from 'node:fs'
import { Catalog, Spec, SwaggerName, ConfigExtension } from '../types'
import { getConfig, getSourceOpenapiEnpoint } from './config'
import { getConfig, getSourceName, getSourceOpenapiEnpoint } from './config'
import { getAvailableTypes } from './swaggers'
import { mergeObjects } from './helpers'
import { generateTypeDefsAndResolversFromSwagger } from './swaggers'
import { directiveTypeDefs } from './directive-typedefs'

export default class ConfigFromSwaggers {
swaggers: SwaggerName[] = []
Expand All @@ -28,7 +27,7 @@ export default class ConfigFromSwaggers {
content?.['application/json']?.schema['$ref'] ?? content?.['*/*']?.schema['$ref']
const schema = ref?.replace('#/components/schemas/', '')
if (schema) {
acc[path] = [query?.operationId, schema, this.swaggers[i]]
acc[path] = [query?.operationId || '', schema, this.swaggers[i]]
}
})
return acc
Expand All @@ -37,18 +36,20 @@ export default class ConfigFromSwaggers {

getInterfacesWithChildren() {
this.specs.forEach((s) => {
const { schemas } = s.components
const entries = Object.entries(schemas).filter(([_, value]) =>
const { schemas } = s.components || {}
const entries = Object.entries(schemas || {}).filter(([_, value]) =>
Object.keys(value).includes('discriminator')
)
for (const [schemaKey, schemaValue] of entries) {
const mapping = schemaValue['discriminator']['mapping'] ?? {}
const mappingTypes = []
mappingTypes.push(
...Object.keys(mapping)
.filter((k) => k !== schemaKey)
.map((k) => mapping[k].replace('#/components/schemas/', ''))
)
const mapping: { [key: string]: string } = schemaValue['discriminator']['mapping'] ?? {}
const mappingTypes: string[] = []
if (Object.keys(mapping).length > 0) {
mappingTypes.push(
...Object.keys(mapping)
.filter((k) => k !== schemaKey)
.map((k) => mapping[k].replace('#/components/schemas/', ''))
)
}
if (this.interfacesWithChildren[schemaKey] === undefined) {
this.interfacesWithChildren[schemaKey] = mappingTypes
} else {
Expand All @@ -71,7 +72,8 @@ export default class ConfigFromSwaggers {
spec,
availableTypes,
this.getInterfacesWithChildren(),
this.catalog
this.catalog,
this.config
)
acc.typeDefs += typeDefs
acc.resolvers = mergeObjects(acc.resolvers, resolvers)
Expand All @@ -84,14 +86,16 @@ export default class ConfigFromSwaggers {
getOpenApiSources() {
return (
this.swaggers.map((source) => ({
name: source,
name: getSourceName(source, this.config),
handler: {
openapi: {
source,
endpoint: getSourceOpenapiEnpoint(source, this.config) || '{env.ENDPOINT}',
ignoreErrorResponses: true,
operationHeaders: {
Authorization: `{context.headers["authorization"]}`
Authorization: `{context.headers["authorization"]}`,
...(this.config.sources?.find((item) => source.includes(item.name))?.handler?.openapi
?.operationHeaders || {})
}
}
}
Expand All @@ -118,15 +122,15 @@ export default class ConfigFromSwaggers {

getMeshConfigFromSwaggers(): {
defaultConfig: any
additionalTypeDefs: string[]
additionalTypeDefs: string
additionalResolvers: any
sources: any[]
} {
const { typeDefs, resolvers } = this.createTypeDefsAndResolvers()

return {
defaultConfig: this.config,
additionalTypeDefs: [typeDefs, directiveTypeDefs],
additionalTypeDefs: typeDefs,
additionalResolvers: resolvers,
sources: [...this.getOpenApiSources(), ...this.getOtherSources()]
}
Expand Down
15 changes: 12 additions & 3 deletions packages/graphql-mesh/utils/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,17 @@ export const getSourceOpenapiEnpoint = (
source: string,
config: YamlConfig.Config
): string | undefined => {
const data = config.sources?.find((item) =>
item?.handler?.openapi?.source?.includes(source.split('/').pop())
)
const data = config.sources?.find((item) => source.includes(item.name))
return data?.handler.openapi?.endpoint
}

/** Get source name from config
* @param source {string} - source name
* @param config {YamlConfig.Config} - config object
* @returns {string} - source name
*
*/
export const getSourceName = (source: string, config: YamlConfig.Config): string => {
const data = config.sources?.find((item) => source.includes(item.name))
return data?.name || source
}
26 changes: 0 additions & 26 deletions packages/graphql-mesh/utils/directive-typedefs/index.ts

This file was deleted.

Loading
Loading