fastify / help

Need help with Fastify? File an Issue here.
https://www.fastify.io/
64 stars 8 forks source link

fastify.inject() in the body or payload cannot send a function in the properties. #984

Closed corteshvictor closed 7 months ago

corteshvictor commented 7 months ago

Prerequisites

Fastify version

4.20.0

Plugin version

No response

Node.js version

20

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

14.2.1

Description

I am unit testing my routes using fastify.inject(), the route receives a form-data with text fields and a file.

The '@fastify/multipart' plugin, enabling attachFieldsToBody to true, injects those fields into the body.

In my route I have a schema that validates some information from the body, but inject cannot send a mock function, it says that the property is required and I cannot invoke the route.

Steps to Reproduce

I have a route similar to this, where I am validating the schema with the plugin "@fastify/type-provider-typebox":"^3.3.0".

fastify.route({
    url: '/create',
    method: 'POST',
    schema: {
    body: Type.Object({
       file: Type.Object({
         filename: Type.String(),
         mimetype: Type.String(),
         toBuffer: Type.Any(),
       })
    })
    },
    handler: async (req, reply) {}
  });

As you can see in the path, I am getting a file with other fields, to do this, I am using the plugin "@fastify/multipart":"^8.1.0". By using this plugin as follows

fastify.register(multipart, { attachFieldsToBody: true });

I am injecting all the form-data into the body so I can validate it in the schema, that file injects a toBuffer() function which I validate it type any in my schema.

When I want to use fastify.inject as follows, the body validates the text fields fine but the toBuffer function, even though it is type any in the schema says it is required because inject does not allow to send that mock function, but if I send a text toBuffer: 'text' if the path is executed.

const response = await app.inject({
      method: 'POST',
      url: '/create',
      body: {
        file: {
          filename: 'test.pdf',
          mimetype: 'application/pdf',
          toBuffer: functionMock()
        },
      },
});

Expected Behavior

The behavior I expect, is that fastify.inject() can allow to send a mock function in the properties and this replaces the one injected in the body when using the @fastify/multipart plugin.

mcollina commented 7 months ago

fastify.inject() is identical to doing an http request. You can't send functions in HTTP requests.

corteshvictor commented 7 months ago

@mcollina I understand what you are saying and I partly agree, but the context that I am explaining to you applies that this allows at least a good functionality.

@fastify/multipart injects toBuffer into the body, and in my scheme I have to place them to be able to inject the types. So you need to be able to dispatch a function in inject to be able to cover the toBuffer that @fastify/multipart adds when you enable { attachFieldsToBody: true }

mcollina commented 7 months ago

If you are not loading @fastify/multipart in your tests, you can simulate that using a preHandler hook.

mcollina commented 7 months ago

Please create a repository

corteshvictor commented 7 months ago

I deleted the previous comment, I was seeing that what I had in mind didn't work for me. I already created the repo and I'll share it with you....

victorhcortesp commented 7 months ago

preHandler doesn't work either because by enabling { attachFieldsToBody: true } the entire form-data is already in the body. Then I will have the same error, toBuffer is required since fastify.inject() does not allow the function to be sent. The schema is validating that body in both the preHandler and the handler.

If I add it in another life cycle before preHandler, multipart has not yet injected the form-data information into body and the JSON schema validation does nothing because the body is empty.

I know it is a complicated case, but it is the behavior of multipart that injects several properties into the body and among that adds the toBuffer function when loading a file in the form-data and the schema I need that toBuffer to arrive in order to have the form types inferred in typescript with the @fastify/type-provider-typebox library

https://github.com/corteshvictor/fastify-demo

Here is the repository. Although I did this quickly with the template that Fastify generates and does not have typescript, but you can see the behavior of the test I want to do.

climba03003 commented 7 months ago

I don't understand, you are trying to send JSON to test multipart endpoint. It is expected to not working. If you want to mock the payload of file, please send a proper multipart/form-data payload to the endpoint.

corteshvictor commented 7 months ago

@climba03003 How do I send a file via fastify.inject()? In addition, the problem is not sending it, it is the validation of the scheme. When validating that toBuffer must be in the body because multipart adds them, fastify.inject() tells you that toBuffer is required.

How would you recommend me to do the unit test with fastify.inject() to send the file and get the toBuffer so I can convert the file?

climba03003 commented 7 months ago

How do I send a file via fastify.inject()?

import fastifyMultipart from '@fastify/multipart'
import Fastify from 'fastify'
import { Readable } from 'node:stream'

const fastify = Fastify()

fastify.register(fastifyMultipart, {
  attachFieldsToBody: true
})

fastify.post('/', {
  schema: {
    body: {
      id: {
        type: 'object',
        properties: {
          value: { type: 'string' },
          mimetype: { type: 'string' }
        },
        required: ['value', 'mimetype']
      },
      file: {
        type: 'object',
        properties: {
          filename: { type: 'string' },
          mimetype: { type: 'string' },
          toBuffer: { }
        },
        required: ['filename', 'mimetype', 'toBuffer']
      }
    }
  }
}, async function(request) {
  const { id, file } = request.body;
  try {
    console.log(id.value) // helloworld
    console.log(await file.toBuffer()) // <Buffer 66 69 6c 65 3a 68 65 6c 6c 6f 77 6f 72 6c 64>
    return { ok: true }
  } catch {
    return { ok: false }
  }
})

await fastify.ready()

const readable = new Readable()
readable.push('--foobar\r\n')
readable.push('Content-Disposition: form-data; name="id"\r\n')
readable.push('Content-Type: text/plain\r\n\r\n')
readable.push('helloworld\r\n')
readable.push('--foobar\r\n')
readable.push('Content-Disposition: form-data; name="file"; filename="file.txt"\r\n')
readable.push('Content-Type: text/plain\r\n\r\n')
// here is the content of file
readable.push('file:helloworld\r\n')
readable.push('--foobar--')
readable.push(null) 

const { body }= await fastify.inject({
  path: '/',
  method: 'POST',
  headers: {
    "content-type": "multipart/form-data; boundary=foobar"
  },
  body: readable
})
console.log(body)

In addition, the problem is not sending it, it is the validation of the scheme.

I really doesn't understand what is the problem. When you send multipart normally, all the property you have will be provided by @fastify/multipart. The schema validation will works as it should.

How would you recommend me to do the unit test with fastify.inject() to send the file and get the toBuffer so I can convert the file?

Send what it should send by a browser. It is the main point of testing. Use a Readable for multipart/form-data or any package to form a proper multipart stream.

mcollina commented 7 months ago

https://www.npmjs.com/package/form-data is what I use.

mcollina commented 7 months ago

I’ve transferred this to help, because it’s mostly a question on how to do something

corteshvictor commented 7 months ago

Thank you very much @climba03003, I will perform the tests tomorrow.

@mcollina I had tried with new formData() but it also failed me, the schema did not realize it and returned the error body must be object. But I did not think to try with new Readable().

I do the tests and I will tell you how it went, but thanks for the help.

corteshvictor commented 7 months ago

@mcollina @climba03003 thank you very much for the help, with the new Readable() it works without problem.

The new fromData() native using Node v20, if placed problems but using the form-data library seems to work passing the headers with the getHeaders() function but this library has several years without being updated and may have security issue.

I decided to stick with new Readable() which worked very well.

Thanks again to both of you for your help.