首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何使用渡槽线束测试文件上传?

如何使用渡槽线束测试文件上传?
EN

Stack Overflow用户
提问于 2019-11-13 22:38:05
回答 2查看 307关注 0票数 0

我遵循了水槽教程来创建测试,但它缺少一个我急需的示例;我无法使用我的控制器测试文件上传端点。

我已经实现了一个这样的控制器:

代码语言:javascript
复制
class FileController extends ResourceController {

  FileController() {
    acceptedContentTypes = [ContentType("multipart", "form-data")];
  }

  @Operation.post()
  Future<Response> postForm() async {

    final transformer = MimeMultipartTransformer(request.raw.headers.contentType.parameters["boundary"]);
    final bodyStream = Stream.fromIterable([await request.body.decode<List<int>>()]);
    final parts = await transformer.bind(bodyStream).toList();

    for (var part in parts) {
      final headers = part.headers;

      HttpMultipartFormData multipart = HttpMultipartFormData.parse(part);
      final content = multipart.cast<List<int>>();

      final filePath = "uploads/test.txt";

      await new File(filePath).create(recursive: true);

      IOSink sink = File(filePath).openWrite();
      await content.forEach(sink.add);

      await sink.flush();
      await sink.close();
    }

    return Response.ok({});   
  }
}

当使用Postman上传文件时,它工作得很好。

现在,我正在尝试为这个端点编写一个测试:

代码语言:javascript
复制
test("POST /upload-file uploads a file to the server", () async {

    final file = File('test.txt');
    final sink = file.openWrite();
    sink.write('test');
    await sink.close();

    final bytes = file.readAsBytesSync();

    harness.agent.headers['Content-Type'] = 'multipart/form-data; boundary=MultipartBoundry';
    harness.agent.headers['Content-Disposition'] = 'form-data; name="file"; filename="test.txt"';


    final response = await harness.agent.post("/upload-file", body: bytes);

    expectResponse(response, 200);
  });

并在vscode调试器中获取以下内容:

代码语言:javascript
复制
Expected: --- HTTP Response ---
          - Status code must be 200
          - Headers can be anything
          - Body can be anything
          ---------------------
  Actual: TestResponse:<-----------
          - Status code is 415
          - Headers are the following:
            - x-frame-options: SAMEORIGIN
            - x-xss-protection: 1; mode=block
            - x-content-type-options: nosniff
            - server: aqueduct/1
            - content-length: 0
          - Body is empty
          -------------------------
          >
   Which: Status codes are different. Expected: 200. Actual: 415
EN

回答 2

Stack Overflow用户

发布于 2019-11-15 03:55:00

415状态码响应将指示ResourceController已经拒绝了请求的内容类型。您已经正确地设置了acceptedContentTypes,但是,隐藏在Agent.headers文档中的测试代理之间存在细微差别(诚然令人困惑

代码语言:javascript
复制
Default headers to be added to requests made by this agent.

By default, this value is the empty map.

Do not provide a 'content-type' key. If the key 'content-type' is present, it will be removed prior to sending the request. It is replaced by the value of TestRequest.contentType, which also controls body encoding.

See also setBasicAuthorization, bearerAuthorization, accept, contentType for setting common headers.

请参阅接口参考here。至于为什么这样存在:与您的响应一样,TestRequest (当您使用代理发出请求时创建和执行的对象)的内容类型决定了将CodecRegistry中的哪个编解码器用作编码器。这允许您始终处理'Dart对象‘,并让水渠处理编码/解码。

票数 0
EN

Stack Overflow用户

发布于 2020-05-25 19:59:08

我写了一堆类来简化和澄清多部分请求测试。因此,如果有人还在为此而苦苦挣扎,欢迎尝试我的解决方案:

测试

代码语言:javascript
复制
import 'multipart_body_parser.dart';
//[...]
    test('POST /upload-file uploads a file to the server', () async {
      final boundary = '7d82a244f2ea5xd0s046';
      final file = File('test.txt');

      var encodedBody = MultipartBodyParser(boundary).parse([
        FileBodyPart(
          'file',
          'test.txt',
          File('test.txt'),
        ),
      ]);

      final response = await harness.agent.post(
        '/upload-file',
        body: encodedBody,
      );

      expectResponse(response, 200);
    });

multipart_body_parser.dart

代码语言:javascript
复制
import 'dart:convert';
import 'dart:io';

class MultipartBodyParser {
  final String boundary;

  MultipartBodyParser(this.boundary)
      : assert(
          boundary != null,
          'The boundary is empty. Please set it ' +
              'and keep on mind that it MUST NOT appear inside any of the ' +
              'encapsulated parts. Example: "sampleBoundary7da24f2e50046".',
        );

  List<int> get encodedNonLastBoundary =>
      ascii.encode('\r\n--' + boundary + '\r\n');

  List<int> get encodedLastBoundary =>
      ascii.encode('\r\n--' + boundary + '--\r\n\r\n');

  List<int> parse(List<_BodyPart> parts) {
    if (parts == null || parts.isEmpty) {
      throw MultipartBodyParserException(
        'Parts CAN NOT be empty. Please set at least one part of body.',
      );
    }
    var body = encodedNonLastBoundary;
    parts.forEach((part) {
      body += part.parse();
      if (parts.last != part) {
        body += encodedNonLastBoundary;
      }
    });
    body += encodedLastBoundary;
    return body;
  }
}

class TextBodyPart extends _BodyPart {
  final String content;

  TextBodyPart(formFieldName, _content)
      : content = _content ?? '',
        super(
          _ContentDisposition(
            formFieldName,
            'form-data',
          ),
          _ContentType(),
        );

  @override
  List<int> get encodedContent => ascii.encode(content);
}

class FileBodyPart extends _BodyPart {
  final File file;
  final String fileName;

  FileBodyPart(formFieldName, this.fileName, this.file)
      : super(
          _ContentDisposition(
            formFieldName,
            'form-data',
            '; filename="$fileName"',
          ),
          _ContentType('application/octet-stream'),
        );

  @override
  List<int> get encodedContent => file.readAsBytesSync();
}

abstract class _BodyPart {
  final _ContentDisposition contentDisposition;
  final _ContentType contentType;

  _BodyPart(this.contentDisposition, this.contentType)
      : assert(contentDisposition != null),
        assert(contentType != null);

  String get partHeader =>
      contentDisposition.toString() + contentType.toString();

  List<int> get encodedContent;

  List<int> parse() => ascii.encode(partHeader) + encodedContent;
}

class _ContentDisposition {
  final String formFieldName;
  final String formFieldType;
  final String additionalParams;
  _ContentDisposition(this.formFieldName, [_formFieldType, _additionalParams])
      : formFieldType = _formFieldType ?? 'form-data',
        additionalParams = _additionalParams ?? '',
        assert(formFieldName != null);

  @override
  String toString() =>
      'content-disposition: $formFieldType; name="$formFieldName"$additionalParams\r\n';
}

class _ContentType {
  final String type;
  _ContentType([this.type = 'text/plain']) : assert(type != null);

  @override
  String toString() => 'content-type: $type\r\n\r\n';
}

class MultipartBodyParserException implements Exception {
  final String message;

  const MultipartBodyParserException([this.message]);
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58839496

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档