├── design ├── logo-negative.png ├── logo-primary.png ├── logo-negative.svg └── logo-primary.svg ├── package.json ├── providers └── ApolloServerProvider.js ├── LICENSE ├── .gitignore ├── src └── ApolloServer │ └── index.js ├── README.md └── docs └── pt-br.md /design/logo-negative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukinco/adonis-apollo-server/HEAD/design/logo-negative.png -------------------------------------------------------------------------------- /design/logo-primary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukinco/adonis-apollo-server/HEAD/design/logo-primary.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lukinco/adonis-apollo-server", 3 | "version": "0.2.0", 4 | "description": "Production-ready Node.js GraphQL server for AdonisJS", 5 | "main": "index.js", 6 | "repository": "git@github.com:lukinco/adonis-apollo-server.git", 7 | "author": "Lukin Co.", 8 | "license": "MIT", 9 | "scripts": { 10 | "postversion": "npm publish --access=public" 11 | }, 12 | "dependencies": { 13 | "apollo-server-core": "2.14.2", 14 | "apollo-server-module-graphiql": "1.4.0", 15 | "graphql-tools": "6.0.16", 16 | "graphql-upload": "11.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /providers/ApolloServerProvider.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { ServiceProvider } = require('@adonisjs/fold') 4 | const ApolloServer = require('../src/ApolloServer') 5 | 6 | class ApolloServerProvider extends ServiceProvider { 7 | /** 8 | * Register AdonisApollo to the IoC container 9 | * with `Adonis/Addons/ApolloServer` namespace. 10 | * 11 | * @method register 12 | * 13 | * @return {void} 14 | */ 15 | register () { 16 | this.app.singleton('Adonis/Addons/ApolloServer', () => { 17 | return new (ApolloServer)() 18 | }) 19 | 20 | this.app.alias('Adonis/Addons/ApolloServer', 'ApolloServer') 21 | } 22 | } 23 | 24 | module.exports = ApolloServerProvider 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Lukin Co. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # System files 107 | .DS_Store 108 | -------------------------------------------------------------------------------- /src/ApolloServer/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { HttpQueryError, runHttpQuery } = require('apollo-server-core') 4 | const GraphiQL = require('apollo-server-module-graphiql') 5 | const { print } = require('graphql') 6 | const { processRequest, GraphQLUpload } = require('graphql-upload') 7 | const { makeExecutableSchema } = require('graphql-tools') 8 | 9 | class ApolloServer { 10 | getQuery (request, response) { 11 | if (request.is('multipart/form-data')) { 12 | return processRequest(request.request, response.response) 13 | } 14 | 15 | return request.method() === 'POST' ? toString(request.post()) : request.get() 16 | } 17 | 18 | async graphql ({ options, request, response, onError }) { 19 | if (!options) { 20 | throw new Error('Apollo Server requires options.') 21 | } 22 | 23 | const schemaWithUpload = { 24 | typeDefs: `${options.schema.typeDefs}\nscalar Upload`, 25 | resolvers: { 26 | ...options.schema.resolvers, 27 | Upload: GraphQLUpload, 28 | }, 29 | } 30 | 31 | options.schema = makeExecutableSchema(schemaWithUpload) 32 | 33 | const query = await this.getQuery(request, response) 34 | 35 | return runHttpQuery([request], { 36 | method: request.method(), 37 | options: options, 38 | query: query, 39 | }).then(({ graphqlResponse }) => { 40 | const parsedResponse = JSON.parse(graphqlResponse) 41 | if (!parsedResponse.errors) { 42 | return response.json(graphqlResponse) 43 | } 44 | 45 | const transformedError = onError ? onError(parsedResponse.errors) : parsedResponse.errors 46 | return response.json({ 47 | errors: transformedError, 48 | data: parsedResponse.data 49 | }) 50 | }, error => { 51 | if ('HttpQueryError' !== error.name) { 52 | throw error 53 | } 54 | 55 | if (error.headers) { 56 | Object.keys(error.headers).forEach(header => { 57 | response.header(header, error.headers[header]) 58 | }) 59 | } 60 | 61 | const errorParsed = JSON.parse(error.message).errors 62 | const transformedError = onError ? onError(errorParsed) : errorParsed 63 | 64 | response.status(error.statusCode).send({ 65 | errors: transformedError 66 | }) 67 | }) 68 | } 69 | 70 | graphiql ({ options, request, response }) { 71 | if (!options) { 72 | throw new Error('Apollo Server GraphiQL requires options.') 73 | } 74 | 75 | const query = request.originalUrl() 76 | 77 | return GraphiQL.resolveGraphiQLString(query, options, request).then(graphiqlString => { 78 | response.header('Content-Type', 'text/html').send(graphiqlString) 79 | }, error => response.send(error)) 80 | } 81 | } 82 | 83 | function toString (value) { 84 | if (Object.prototype.toString.call(value.query) === '[object String]') { 85 | return value 86 | } 87 | 88 | return { 89 | query: print(value.query) 90 | } 91 | } 92 | 93 | module.exports = ApolloServer 94 | -------------------------------------------------------------------------------- /design/logo-negative.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /design/logo-primary.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # adonis-apollo-server 2 | 3 |

4 | :us: English  |   :brazil: Português do Brasil 5 |

6 | 7 |

8 | 9 | > GraphQL implementation using Apollo Server for Adonis 10 | 11 | This package integrates Apollo GraphQL Server with the AdonisJS framework. It allows you to use Apollo server in your AdonisJS app. 12 | 13 | > **NOTE:** This package requires [@adonisjs/bodyparser](https://github.com/adonisjs/adonis-bodyparser) and [graphql](https://github.com/graphql/graphql-js) 14 | 15 | ## Installation 16 | 17 | ```bash 18 | yarn add --exact @lukinco/adonis-apollo-server 19 | ``` 20 | 21 | ### Registering provider 22 | 23 | Make sure to register the provider inside `start/app.js` file. 24 | 25 | ```js 26 | const providers = [ 27 | '@lukinco/adonis-apollo-server/providers/ApolloServerProvider' 28 | ] 29 | ``` 30 | 31 | That's all! 32 | 33 | ## Usage 34 | 35 | Now you can use the provider by pulling it from IoC container 36 | 37 | ```js 38 | // start/routes.js 39 | 40 | 'use strict' 41 | 42 | const Route = use('Route') 43 | const ApolloServer = use('ApolloServer') 44 | 45 | const typeDefs = ` 46 | type Query { 47 | testString: String 48 | } 49 | ` 50 | 51 | const resolvers = { 52 | Query: { 53 | testString () { 54 | return 'Seems to be working!' 55 | } 56 | } 57 | } 58 | 59 | const schema = { typeDefs, resolvers } 60 | 61 | Route.post('/graphql', ({ auth, request, response }) => { 62 | return ApolloServer.graphql({ 63 | options: { 64 | schema, 65 | context: { auth } 66 | }, 67 | request, 68 | response, 69 | onError: (errors) => { 70 | // this function is optional. "errors" is an array of all errors. 71 | // You may show the errors the way you want to. 72 | // If this function is defined, you must return an array of errors. 73 | } 74 | }) 75 | }) 76 | 77 | Route.get('/graphiql', ({ request, response }) => { 78 | return ApolloServer.graphiql({ 79 | options: { endpointURL: '/graphql' }, 80 | request, 81 | response 82 | }) 83 | }) 84 | ``` 85 | 86 | ## Uploads 87 | 88 | To make uploads, you must set `autoProcess: false` on `config/bodyParser.js`. 89 | 90 | You must define the field that will receive the uploaded file with scalar type `Upload`: 91 | 92 | ```gql 93 | type Mutation { 94 | upload (file: Upload!): String 95 | } 96 | ``` 97 | 98 | #### Usage 99 | 100 | You may create a helper file in `app/Helpers/upload.js`: 101 | 102 | ```js 103 | 'use strict' 104 | 105 | const fs = require('fs') 106 | const { join } = require('path') 107 | const slugify = require('slugify') 108 | const { v4: uuidv4 } = require('uuid') 109 | const Env = use('Env') 110 | 111 | const UPLOAD_DIRNAME = 'uploads' 112 | const FULL_UPLOAD_PATH = join(process.cwd(), 'public', UPLOAD_DIRNAME) 113 | 114 | fs.mkdir(FULL_UPLOAD_PATH, { recursive: true }, () => {}) 115 | 116 | module.exports = async (file) => { 117 | const { createReadStream, filename } = await file 118 | const fileStream = createReadStream() 119 | const newFilename = `${uuidv4()}-${slugify(filename, { lower: true })}` 120 | 121 | const pathToUploadFile = join(FULL_UPLOAD_PATH, newFilename) 122 | const uploadDistStream = fs.createWriteStream(pathToUploadFile) 123 | 124 | await new Promise((resolve, reject) => { 125 | uploadDistStream.on('finish', resolve) 126 | 127 | uploadDistStream.on('error', (error) => { 128 | console.log('error', error) 129 | fs.unlink(pathToUploadFile, () => reject(error)) 130 | }) 131 | 132 | fileStream.on('error', (error) => uploadDistStream.destroy(error)) 133 | 134 | fileStream.pipe(uploadDistStream) 135 | }) 136 | 137 | return `${Env.get('UPLOAD_HOST')}/${UPLOAD_DIRNAME}/${newFilename}` 138 | } 139 | ``` 140 | 141 | Libs `slugify` and `uuid` are optional, but they will help you to create unique filenames. 142 | 143 | You can use an env var `UPLOAD_HOST`, like my example, if you want to upload files in different places by environment. 144 | 145 | Then, when you want to make an upload, you just have to use that function, passing the file received as argument, and the function will return a Promise, that resolves to a full path of the file uploaded. 146 | 147 | To send a file, look the next examples. 148 | 149 | #### How to test using Insomnia 150 | 151 | On Insomnia, you must use Multipart requests, and send 3 fields: 152 | 153 | - `operations`: a JSON with `query` and `variables` keys: 154 | 155 | ```json 156 | { 157 | "query": "mutation ($file: Upload!) {\n upload (file: $file) \n}", 158 | "variables": { 159 | "file": null 160 | } 161 | } 162 | ``` 163 | 164 | - `map`: a JSON mapping the file that will be upload with your variables set in `operations`: 165 | 166 | ```json 167 | { 168 | "0": ["variables.file"] 169 | } 170 | ``` 171 | 172 | - `0`: the file itself. `0` is used here because we set in `map` above that `0` will point to our `variables.file` entry. 173 | 174 | That's it :) 175 | 176 | #### How to write tests 177 | 178 | In Adonis v4, you have to install `axios` and `form-data` libraries to make the upload work on server side. 179 | 180 | Then, you can use a helper to upload file like this: 181 | 182 | ```js 183 | const fs = require('fs') 184 | const gql = require('graphql-tag') 185 | const { print } = require('graphql/language/printer') 186 | const axios = require('axios') 187 | const FormData = require('form-data') 188 | const Env = use('Env') 189 | 190 | async function requestQuery ({ client, token, query, variables, file }) { 191 | if (token) { 192 | response = response.header('Authorization', `Bearer ${token}`) 193 | } 194 | 195 | const operations = { 196 | query: print(query), 197 | variables, 198 | } 199 | 200 | file = typeof file === 'string' 201 | ? { field: 'file', attach: file } 202 | : file 203 | 204 | const variableInput = Object.keys(variables)[0] === file.field 205 | ? `${file.field}` 206 | : `${Object.keys(variables)[0]}.${file.field}` 207 | 208 | const map = { 209 | 0: [`variables.${variableInput}`], 210 | } 211 | 212 | const form = new FormData() 213 | form.append('operations', JSON.stringify(operations)) 214 | form.append('map', JSON.stringify(map)) 215 | form.append('0', fs.createReadStream(file.attach)) 216 | 217 | return axios 218 | .post(`${Env.get('APP_URL')}/graphql`, form, { 219 | headers: { 220 | authorization: `Bearer ${token}`, 221 | ...form.getHeaders(), 222 | }, 223 | }) 224 | } 225 | ``` 226 | 227 | Then, just write your test: 228 | 229 | ```js 230 | const fs = require('fs') 231 | const { test, trait } = use('Test/Suite')('Upload') 232 | const Helpers = use('Helpers') 233 | const gql = require('graphql-tag') 234 | const { expect } = require('chai') 235 | 236 | trait('Test/ApiClient') 237 | trait('Auth/Client') 238 | 239 | const UPLOAD = gql` 240 | mutation ($file: Upload!) { 241 | upload (file: $file) { 242 | location 243 | } 244 | } 245 | ` 246 | 247 | test('Should upload an image with mimetype PNG', async ({ client }) => { 248 | const file = Helpers.tmpPath('file.png') 249 | fs.writeFile(file, 'TEST', () => {}) 250 | 251 | const response = await requestQuery({ 252 | client, 253 | token, // use a token if you only want allow uploads for logged in users 254 | query: UPLOAD, 255 | variables: { file: null }, 256 | file, 257 | }) 258 | 259 | const result = response.data 260 | 261 | expect(response.status).to.be.equal(200) 262 | expect(result).to.have.keys('data') 263 | expect(result.data).to.have.keys(['upload']) 264 | expect(result.data.upload).to.be.a('string') 265 | }) 266 | ``` 267 | 268 | ## License 269 | 270 | MIT 271 | -------------------------------------------------------------------------------- /docs/pt-br.md: -------------------------------------------------------------------------------- 1 | # adonis-apollo-server 2 | 3 |

4 | :us: English  |   :brazil: Português do Brasil 5 |

6 | 7 |

8 | 9 | > Implementação do GraphQL usando Apollo Server para Adonis 10 | 11 | Este pacote integra o Apollo GraphQL Server com o framework AdonisJS. Ele permite que você use o Apollo Server em sua aplicação AdonisJS. 12 | 13 | > **NOTA:** Este pacote requer [@adonisjs/bodyparser](https://github.com/adonisjs/adonis-bodyparser) e [graphql](https://github.com/graphql/graphql-js) 14 | 15 | ## Instalação 16 | 17 | ```bash 18 | yarn add --exact @lukinco/adonis-apollo-server 19 | ``` 20 | 21 | ### Registrando provider 22 | 23 | Tenha certeza de registrar o provider no arquivo `start/app.js`. 24 | 25 | ```js 26 | const providers = [ 27 | '@lukinco/adonis-apollo-server/providers/ApolloServerProvider' 28 | ]; 29 | ``` 30 | 31 | Isso é tudo! 32 | 33 | ## Uso 34 | 35 | Agora você pode usar o provider pegando ele do container IoC 36 | 37 | ```js 38 | // start/routes.js 39 | 40 | 'use strict' 41 | 42 | const Route = use('Route') 43 | const ApolloServer = use('ApolloServer') 44 | 45 | const typeDefs = ` 46 | type Query { 47 | testString: String 48 | } 49 | ` 50 | 51 | const resolvers = { 52 | Query: { 53 | testString () { 54 | return 'Seems to be working!' 55 | } 56 | } 57 | } 58 | 59 | const schema = { typeDefs, resolvers } 60 | 61 | Route.post('/graphql', ({ auth, request, response }) => { 62 | return ApolloServer.graphql({ 63 | options: { 64 | schema, 65 | context: { auth } 66 | }, 67 | request, 68 | response, 69 | onError: (errors) => { 70 | // Esta função é opcional. "errors" é um array de todos os erros. 71 | // Você pode mostrar os erros da maneira que quiser. 72 | // Se esta função for definida, você precisa retornar um array de erros. 73 | } 74 | }) 75 | }) 76 | 77 | Route.get('/graphiql', ({ request, response }) => { 78 | return ApolloServer.graphiql({ 79 | options: { endpointURL: '/graphql' }, 80 | request, 81 | response 82 | }) 83 | }) 84 | ``` 85 | 86 | ## Uploads 87 | 88 | Para fazer uploads, você precisa configurar `autoProcess: false` em `config/bodyParser.js`. 89 | 90 | Você precisa definir o campo que vai receber o arquivo do upload com o tipo escalar `Upload`: 91 | 92 | ```gql 93 | type Mutation { 94 | upload (file: Upload!): String 95 | } 96 | ``` 97 | 98 | #### Uso 99 | 100 | Você pode criar um arquivo 'helper' em `app/Helpers/upload.js`: 101 | 102 | ```js 103 | 'use strict' 104 | 105 | const fs = require('fs') 106 | const { join } = require('path') 107 | const slugify = require('slugify') 108 | const { v4: uuidv4 } = require('uuid') 109 | const Env = use('Env') 110 | 111 | const UPLOAD_DIRNAME = 'uploads' 112 | const FULL_UPLOAD_PATH = join(process.cwd(), 'public', UPLOAD_DIRNAME) 113 | 114 | fs.mkdir(FULL_UPLOAD_PATH, { recursive: true }, () => {}) 115 | 116 | module.exports = async (file) => { 117 | const { createReadStream, filename } = await file 118 | const fileStream = createReadStream() 119 | const newFilename = `${uuidv4()}-${slugify(filename, { lower: true })}` 120 | 121 | const pathToUploadFile = join(FULL_UPLOAD_PATH, newFilename) 122 | const uploadDistStream = fs.createWriteStream(pathToUploadFile) 123 | 124 | await new Promise((resolve, reject) => { 125 | uploadDistStream.on('finish', resolve) 126 | 127 | uploadDistStream.on('error', (error) => { 128 | console.log('error', error) 129 | fs.unlink(pathToUploadFile, () => reject(error)) 130 | }) 131 | 132 | fileStream.on('error', (error) => uploadDistStream.destroy(error)) 133 | 134 | fileStream.pipe(uploadDistStream) 135 | }) 136 | 137 | return `${Env.get('UPLOAD_HOST')}/${UPLOAD_DIRNAME}/${newFilename}` 138 | } 139 | ``` 140 | 141 | As bibliotecas `slugify` e `uuid` são opcionais, mas elas vão ajudar você a criar nomes únicos de arquivos. 142 | 143 | Você pode usar uma variável env `UPLOAD_HOST`, como no exemplo, se você quer fazer upload de arquivos em diferentes ambientes. 144 | 145 | Então, quando você quiser fazer um upload, você só tem que usar esta função, passando o arquivo recebido como argumento, e a função vai retornar uma Promise, que resolve o caminho completo do arquivo carregado. 146 | 147 | Para enviar um arquivo, dê uma olhada nos próximos exemplos. 148 | 149 | ### Como testar usando Insomnia 150 | 151 | No Insomnia, você precisa usar requisições Multipart, e enviar 3 campos: 152 | 153 | - `operations`: um JSON com chaves `query` e `variables`: 154 | 155 | ```json 156 | { 157 | "query": "mutation ($file: Upload!) {\n upload (file: $file) \n}", 158 | "variables": { 159 | "file": null 160 | } 161 | } 162 | ``` 163 | 164 | - `map`: um JSON mapeando o arquivo que vai ser carregado com suas variáveis configuradas em `operations`: 165 | 166 | 167 | ```json 168 | { 169 | "0": ["variables.file"] 170 | } 171 | ``` 172 | 173 | - `0`: o arquivo em si. `0` é usado aqui porque nós configuramos no `map` acima que `0` vai apontar para nossa entrada `variables.file`. 174 | 175 | É isso :) 176 | 177 | #### Como escrever testes 178 | 179 | No Adonis v4, você precisa instalar as bibliotecas `axios` e `form-data` para fazer o upload funcionar no lado do servidor. 180 | 181 | Então, você pode usar um helper para upload, como este: 182 | 183 | ```js 184 | const fs = require('fs') 185 | const gql = require('graphql-tag') 186 | const { print } = require('graphql/language/printer') 187 | const axios = require('axios') 188 | const FormData = require('form-data') 189 | const Env = use('Env') 190 | 191 | async function requestQuery ({ client, token, query, variables, file }) { 192 | if (token) { 193 | response = response.header('Authorization', `Bearer ${token}`) 194 | } 195 | 196 | const operations = { 197 | query: print(query), 198 | variables, 199 | } 200 | 201 | file = typeof file === 'string' 202 | ? { field: 'file', attach: file } 203 | : file 204 | 205 | const variableInput = Object.keys(variables)[0] === file.field 206 | ? `${file.field}` 207 | : `${Object.keys(variables)[0]}.${file.field}` 208 | 209 | const map = { 210 | 0: [`variables.${variableInput}`], 211 | } 212 | 213 | const form = new FormData() 214 | form.append('operations', JSON.stringify(operations)) 215 | form.append('map', JSON.stringify(map)) 216 | form.append('0', fs.createReadStream(file.attach)) 217 | 218 | return axios 219 | .post(`${Env.get('APP_URL')}/graphql`, form, { 220 | headers: { 221 | authorization: `Bearer ${token}`, 222 | ...form.getHeaders(), 223 | }, 224 | }) 225 | } 226 | ``` 227 | 228 | Então, simplesmente escreva seu teste: 229 | 230 | ```js 231 | const fs = require('fs') 232 | const { test, trait } = use('Test/Suite')('Upload') 233 | const Helpers = use('Helpers') 234 | const gql = require('graphql-tag') 235 | const { expect } = require('chai') 236 | 237 | trait('Test/ApiClient') 238 | trait('Auth/Client') 239 | 240 | const UPLOAD = gql` 241 | mutation ($file: Upload!) { 242 | upload (file: $file) { 243 | location 244 | } 245 | } 246 | ` 247 | 248 | test('Should upload an image with mimetype PNG', async ({ client }) => { 249 | const file = Helpers.tmpPath('file.png') 250 | fs.writeFile(file, 'TEST', () => {}) 251 | 252 | const response = await requestQuery({ 253 | client, 254 | token, // use um token se você quer permitir uploads somente para usuários autenticados 255 | query: UPLOAD, 256 | variables: { file: null }, 257 | file, 258 | }) 259 | 260 | const result = response.data 261 | 262 | expect(response.status).to.be.equal(200) 263 | expect(result).to.have.keys('data') 264 | expect(result.data).to.have.keys(['upload']) 265 | expect(result.data.upload).to.be.a('string') 266 | }) 267 | ``` 268 | 269 | ## Licença 270 | 271 | MIT 272 | --------------------------------------------------------------------------------