如果 headers 和 body 都存在,Pact.io(打字稿)失败
Pact.io (typescript) fails if both headers and body are present
我在前端 (TypeScript) 和后端之间使用 Pact.io 合同。我正在尝试为文件上传编写协议;但是,如果我在 withRequest
部分同时指定 headers.Content-Type
和 body
,验证显然会无缘无故地失败(在前端 -> 甚至没有生成协议)。如果我遗漏了这两个部分中的任何一个,验证就会起作用(但当然会丢失一些数据,所以我无法成功验证后端)。我的设置如下所示:
const fileBody = '------boundary\r\n' +
'Content-Disposition: form-data; name="file"; ' +
'filename="test-attachment.pdf"\r\n' +
'Content-Type: application/pdf\r\n' +
'\r\n' +
'file content\r\n' +
'------boundary--\r\n';
const likeFileBody = fileBody.replace(/boundary/g, ".*");
const interaction: InteractionObject = {
state: "my state",
uponReceiving: "a file upload",
withRequest: {
method: 'POST',
path: `/my/upload/path`,
headers: {
"Content-Type": Matchers.regex({
generate: "multipart/form-data; boundary=----boundary",
matcher: "multipart/form-data; boundary=.*",
})
},
body: Matchers.regex({
generate: fileBody,
matcher: likeFileBody,
}),
},
willRespondWith: {
status: 201,
headers: {'Content-Type': 'application/json'},
body: {"some": "data"},
}
};
await provider.addInteraction(interaction);
// ng2-file-upload module
const uploader = new FileUploader({
url: `/my/upload/path`
});
uploader.addToQueue([new File(["file content"], "test-attachment.pdf")]);
uploader.uploadAll();
// poll uploader status
return new Promise(async resolve => {
while (uploader.isUploading) {
await new Promise((pollResolve => setTimeout(pollResolve, 10)));
}
resolve();
});
协议日志如下:
INFO -- : Registered expected interaction POST /my/upload/path
DEBUG -- : {
"description": "a file upload",
"providerState": "my state",
"request": {
"method": "POST",
"path": "/my/upload/path",
"headers": {
"Content-Type": {
"json_class": "Pact::Term",
"data": {
"generate": "multipart/form-data; boundary=----boundary",
"matcher": {"json_class":"Regexp","o":0,"s":"multipart/form-data; boundary=.*"}
}
}
},
"body": {
"json_class": "Pact::Term",
"data": {
"generate": "------boundary\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test-attachment.pdf\"\r\nContent-Type: application/pdf\r\n\r\nfile content\r\n------boundary--\r\n",
"matcher": {"json_class":"Regexp","o":0,"s":"------.*\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test-attachment.pdf\"\r\nContent-Type: application/pdf\r\n\r\nfile content\r\n------.*--\r
\n"}
}
}
},
"response": {
"status": 201,
"headers": {
"Content-Type": "application/json"
},
"body": {}
}
}
INFO -- : Received request POST /my/upload/path
DEBUG -- : {
"path": "/my/upload/path",
"query": "",
"method": "post",
"body": "----------------------------163633642410335919983066\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test-attachment.pdf\"\r\nContent-Type: application/pdf\r\n\r\nfile content\r\n----------
------------------163633642410335919983066--\r\n",
"headers": {
"Content-Length": "234",
"Content-Type": "multipart/form-data; boundary=--------------------------163633642410335919983066",
"Referer": "http://localhost:8991/",
"User-Agent": "Mozilla/5.0 (linux) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/11.12.0",
"Accept-Language": "en",
"Accept": "*/*",
"Host": "localhost:8991",
"Accept-Encoding": "gzip, deflate",
"Connection": "close",
"Version": "HTTP/1.1"
}
}
ERROR -- : Error ocurred in mock service: NoMethodError - undefined method `split' for #<Pact::Term:0x00561356747038>
ERROR -- : /srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-support-1.8.0/lib/pact/shared/multipart_form_differ.
rb:8:in `call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-support-1.8.0/lib/pact/consumer_contract/request.rb:72:in `body_diff'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-support-1.8.0/lib/pact/consumer_contract/request.rb:42:in `difference'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-support-1.8.0/lib/pact/consumer_contract/request.rb:28:in `matches?'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/mock_service/interactions/candidate_interactions.rb:8:in `block in matc
hing_interactions'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/mock_service/interactions/candidate_interactions.rb:7:in `select'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/mock_service/interactions/candidate_interactions.rb:7:in `matching_inte
ractions'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/mock_service/request_handlers/interaction_replay.rb:51:in `find_respons
e'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/mock_service/request_handlers/interaction_replay.rb:41:in `respond'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/mock_service/request_handlers/base_request_handler.rb:17:in `call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/rack-2.0.5/lib/rack/cascade.rb:33:in `block in call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/rack-2.0.5/lib/rack/cascade.rb:24:in `each'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/rack-2.0.5/lib/rack/cascade.rb:24:in `call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/consumer/mock_service/cors_origin_header_middleware.rb:11:in `call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/consumer/mock_service/error_handler.rb:13:in `call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/mock_service/app.rb:33:in `call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/consumer/mock_service/set_location.rb:14:in `call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/rack-2.0.5/lib/rack/handler/webrick.rb:86:in `service'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/webrick-1.3.1/lib/webrick/httpserver.rb:138:in `service'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/webrick-1.3.1/lib/webrick/httpserver.rb:94:in `run'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/webrick-1.3.1/lib/webrick/server.rb:191:in `block in start_thread'
WARN -- : Verifying - actual interactions do not match expected interactions.
Missing requests:
POST /my/upload/path
WARN -- : Missing requests:
POST /my/upload/path
我是不是遗漏了什么或者这是契约框架的错误?
目前您不能将正文匹配器应用于 JSON 以外的内容类型。
官方指导是测试交互的其他方面而不是二进制负载本身:https://docs.pact.io/faq#how-do-i-test-binary-files-in-responses-such-as-a-download
查看pact框架的源码后,发现它已经用通配符替换了边界;但是,它仅在 body 中而不是在 Content-Type
header 中这样做。因此,该协定的正确表述是:
const fileBody = '------boundary\r\n' +
'Content-Disposition: form-data; name="file"; ' +
'filename="test-attachment.pdf"\r\n' +
'Content-Type: application/pdf\r\n' +
'\r\n' +
'file content\r\n' +
'------boundary--\r\n';
...
headers: {
"Content-Type": Matchers.regex({
generate: "multipart/form-data; boundary=----boundary",
matcher: "multipart/form-data; boundary=.*",
})
},
body: fileBody,
有了这个,我能够在消费者和提供者中进行验证。
我在前端 (TypeScript) 和后端之间使用 Pact.io 合同。我正在尝试为文件上传编写协议;但是,如果我在 withRequest
部分同时指定 headers.Content-Type
和 body
,验证显然会无缘无故地失败(在前端 -> 甚至没有生成协议)。如果我遗漏了这两个部分中的任何一个,验证就会起作用(但当然会丢失一些数据,所以我无法成功验证后端)。我的设置如下所示:
const fileBody = '------boundary\r\n' +
'Content-Disposition: form-data; name="file"; ' +
'filename="test-attachment.pdf"\r\n' +
'Content-Type: application/pdf\r\n' +
'\r\n' +
'file content\r\n' +
'------boundary--\r\n';
const likeFileBody = fileBody.replace(/boundary/g, ".*");
const interaction: InteractionObject = {
state: "my state",
uponReceiving: "a file upload",
withRequest: {
method: 'POST',
path: `/my/upload/path`,
headers: {
"Content-Type": Matchers.regex({
generate: "multipart/form-data; boundary=----boundary",
matcher: "multipart/form-data; boundary=.*",
})
},
body: Matchers.regex({
generate: fileBody,
matcher: likeFileBody,
}),
},
willRespondWith: {
status: 201,
headers: {'Content-Type': 'application/json'},
body: {"some": "data"},
}
};
await provider.addInteraction(interaction);
// ng2-file-upload module
const uploader = new FileUploader({
url: `/my/upload/path`
});
uploader.addToQueue([new File(["file content"], "test-attachment.pdf")]);
uploader.uploadAll();
// poll uploader status
return new Promise(async resolve => {
while (uploader.isUploading) {
await new Promise((pollResolve => setTimeout(pollResolve, 10)));
}
resolve();
});
协议日志如下:
INFO -- : Registered expected interaction POST /my/upload/path
DEBUG -- : {
"description": "a file upload",
"providerState": "my state",
"request": {
"method": "POST",
"path": "/my/upload/path",
"headers": {
"Content-Type": {
"json_class": "Pact::Term",
"data": {
"generate": "multipart/form-data; boundary=----boundary",
"matcher": {"json_class":"Regexp","o":0,"s":"multipart/form-data; boundary=.*"}
}
}
},
"body": {
"json_class": "Pact::Term",
"data": {
"generate": "------boundary\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test-attachment.pdf\"\r\nContent-Type: application/pdf\r\n\r\nfile content\r\n------boundary--\r\n",
"matcher": {"json_class":"Regexp","o":0,"s":"------.*\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test-attachment.pdf\"\r\nContent-Type: application/pdf\r\n\r\nfile content\r\n------.*--\r
\n"}
}
}
},
"response": {
"status": 201,
"headers": {
"Content-Type": "application/json"
},
"body": {}
}
}
INFO -- : Received request POST /my/upload/path
DEBUG -- : {
"path": "/my/upload/path",
"query": "",
"method": "post",
"body": "----------------------------163633642410335919983066\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test-attachment.pdf\"\r\nContent-Type: application/pdf\r\n\r\nfile content\r\n----------
------------------163633642410335919983066--\r\n",
"headers": {
"Content-Length": "234",
"Content-Type": "multipart/form-data; boundary=--------------------------163633642410335919983066",
"Referer": "http://localhost:8991/",
"User-Agent": "Mozilla/5.0 (linux) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/11.12.0",
"Accept-Language": "en",
"Accept": "*/*",
"Host": "localhost:8991",
"Accept-Encoding": "gzip, deflate",
"Connection": "close",
"Version": "HTTP/1.1"
}
}
ERROR -- : Error ocurred in mock service: NoMethodError - undefined method `split' for #<Pact::Term:0x00561356747038>
ERROR -- : /srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-support-1.8.0/lib/pact/shared/multipart_form_differ.
rb:8:in `call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-support-1.8.0/lib/pact/consumer_contract/request.rb:72:in `body_diff'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-support-1.8.0/lib/pact/consumer_contract/request.rb:42:in `difference'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-support-1.8.0/lib/pact/consumer_contract/request.rb:28:in `matches?'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/mock_service/interactions/candidate_interactions.rb:8:in `block in matc
hing_interactions'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/mock_service/interactions/candidate_interactions.rb:7:in `select'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/mock_service/interactions/candidate_interactions.rb:7:in `matching_inte
ractions'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/mock_service/request_handlers/interaction_replay.rb:51:in `find_respons
e'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/mock_service/request_handlers/interaction_replay.rb:41:in `respond'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/mock_service/request_handlers/base_request_handler.rb:17:in `call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/rack-2.0.5/lib/rack/cascade.rb:33:in `block in call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/rack-2.0.5/lib/rack/cascade.rb:24:in `each'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/rack-2.0.5/lib/rack/cascade.rb:24:in `call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/consumer/mock_service/cors_origin_header_middleware.rb:11:in `call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/consumer/mock_service/error_handler.rb:13:in `call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/mock_service/app.rb:33:in `call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-mock_service-2.12.0/lib/pact/consumer/mock_service/set_location.rb:14:in `call'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/rack-2.0.5/lib/rack/handler/webrick.rb:86:in `service'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/webrick-1.3.1/lib/webrick/httpserver.rb:138:in `service'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/webrick-1.3.1/lib/webrick/httpserver.rb:94:in `run'
/srv/service/node_modules/@pact-foundation/pact-node/standalone/linux-x64-1.61.1/lib/vendor/ruby/2.2.0/gems/webrick-1.3.1/lib/webrick/server.rb:191:in `block in start_thread'
WARN -- : Verifying - actual interactions do not match expected interactions.
Missing requests:
POST /my/upload/path
WARN -- : Missing requests:
POST /my/upload/path
我是不是遗漏了什么或者这是契约框架的错误?
目前您不能将正文匹配器应用于 JSON 以外的内容类型。
官方指导是测试交互的其他方面而不是二进制负载本身:https://docs.pact.io/faq#how-do-i-test-binary-files-in-responses-such-as-a-download
查看pact框架的源码后,发现它已经用通配符替换了边界;但是,它仅在 body 中而不是在 Content-Type
header 中这样做。因此,该协定的正确表述是:
const fileBody = '------boundary\r\n' +
'Content-Disposition: form-data; name="file"; ' +
'filename="test-attachment.pdf"\r\n' +
'Content-Type: application/pdf\r\n' +
'\r\n' +
'file content\r\n' +
'------boundary--\r\n';
...
headers: {
"Content-Type": Matchers.regex({
generate: "multipart/form-data; boundary=----boundary",
matcher: "multipart/form-data; boundary=.*",
})
},
body: fileBody,
有了这个,我能够在消费者和提供者中进行验证。