add:output api call details and curl
This commit is contained in:
parent
936714242f
commit
0585dd0b29
42
apis0.txt
Normal file
42
apis0.txt
Normal file
@ -0,0 +1,42 @@
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/message/push/example_schema/example_version
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/message/push/example_schema/example_version
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/message/push/example_schema/example_version
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/message/push/example_schema/example_version
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 488' -d '{"isSearchCount": "not-a-boolean", "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/message/push/example_schema/example_version
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/message/push/example_schema/example_version
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/message/push/example_schema/example_version
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0?pageNo=query_val_pageNo&pageSize=query_val_pageSize'
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0?pageNo=query_val_pageNo&pageSize=query_val_pageSize'
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0?pageNo=query_val_pageNo&pageSize=query_val_pageSize'
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0?pageNo=12345&pageSize=query_val_pageSize'
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 488' -d '{"isSearchCount": "not-a-boolean", "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0?pageNo=query_val_pageNo&pageSize=query_val_pageSize'
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0?pageNo=query_val_pageNo&pageSize=query_val_pageSize'
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0?pageNo=query_val_pageNo&pageSize=query_val_pageSize'
|
||||
curl -X PUT -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 53' -d '{"id": "example_string", "version": "example_string"}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit?id=dsid'
|
||||
curl -X PUT -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 53' -d '{"id": "example_string", "version": "example_string"}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit?id=dsid'
|
||||
curl -X PUT -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 53' -d '{"id": "example_string", "version": "example_string"}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit?id=dsid'
|
||||
curl -X PUT -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 53' -d '{"id": "example_string", "version": "example_string"}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit?id=12345'
|
||||
curl -X PUT -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 42' -d '{"id": 12345, "version": "example_string"}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit?id=dsid'
|
||||
curl -X PUT -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 29' -d '{"version": "example_string"}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit?id=dsid'
|
||||
curl -X PUT -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 53' -d '{"id": "example_string", "version": "example_string"}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit
|
||||
curl -X DELETE -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 57' -d '{"version": "example_string", "data": ["example_string"]}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit?id=dsid'
|
||||
curl -X DELETE -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 57' -d '{"version": "example_string", "data": ["example_string"]}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit?id=dsid'
|
||||
curl -X DELETE -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 57' -d '{"version": "example_string", "data": ["example_string"]}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit?id=dsid'
|
||||
curl -X DELETE -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 57' -d '{"version": "example_string", "data": ["example_string"]}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit?id=12345'
|
||||
curl -X DELETE -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 46' -d '{"version": 12345, "data": ["example_string"]}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit?id=dsid'
|
||||
curl -X DELETE -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 57' -d '{"version": "example_string", "data": ["example_string"]}' --insecure 'http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit?id=dsid'
|
||||
curl -X DELETE -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 57' -d '{"version": "example_string", "data": ["example_string"]}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 152' -d '{"version": "example_string", "data": [{"bsflag": 0.0, "wellCommonName": "example_string", "wellId": "example_string", "dataRegion": "example_string"}]}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 152' -d '{"version": "example_string", "data": [{"bsflag": 0.0, "wellCommonName": "example_string", "wellId": "example_string", "dataRegion": "example_string"}]}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 152' -d '{"version": "example_string", "data": [{"bsflag": 0.0, "wellCommonName": "example_string", "wellId": "example_string", "dataRegion": "example_string"}]}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 152' -d '{"version": "example_string", "data": [{"bsflag": 0.0, "wellCommonName": "example_string", "wellId": "example_string", "dataRegion": "example_string"}]}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 141' -d '{"version": 12345, "data": [{"bsflag": 0.0, "wellCommonName": "example_string", "wellId": "example_string", "dataRegion": "example_string"}]}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 137' -d '{"version": "example_string", "data": [{"wellCommonName": "example_string", "wellId": "example_string", "dataRegion": "example_string"}]}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit
|
||||
curl -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 152' -d '{"version": "example_string", "data": [{"bsflag": 0.0, "wellCommonName": "example_string", "wellId": "example_string", "dataRegion": "example_string"}]}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit
|
||||
curl -X GET -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0/example_id
|
||||
curl -X GET -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0/example_id
|
||||
curl -X GET -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0/example_id
|
||||
curl -X GET -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0/example_id
|
||||
curl -X GET -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 488' -d '{"isSearchCount": "not-a-boolean", "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0/example_id
|
||||
curl -X GET -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0/example_id
|
||||
curl -X GET -H 'User-Agent: python-requests/2.32.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'tenant-id: header_val_tenant-id' -H 'Content-Type: application/json' -H 'Content-Length: 477' -d '{"isSearchCount": true, "query": {"dataRegions": ["example_string"], "fields": ["example_string"], "filter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "groupFields": ["example_string"], "groupFilter": {"key": "example_string", "logic": "example_string", "realValue": [{}], "singleValue": {}, "subFilter": ["example_string"], "symbol": "example_string"}, "sort": {}}}' --insecure http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0/example_id
|
||||
1961
apis0.yaml
Normal file
1961
apis0.yaml
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@ -1,6 +1,15 @@
|
||||
"""API Caller Module"""
|
||||
import requests
|
||||
from typing import Any, Dict, Optional, Union, List
|
||||
import json # Added for cURL body pretty printing
|
||||
from typing import Any, Dict, Optional, Union, List, Tuple # Added Tuple
|
||||
import shlex # Added for shell quoting
|
||||
import urllib.parse # Moved import to top level for reuse
|
||||
|
||||
# Attempt to import curlify, if not found, the function will raise an error or fallback
|
||||
try:
|
||||
import curlify
|
||||
except ImportError:
|
||||
curlify = None # So we can check its availability
|
||||
|
||||
from pydantic import BaseModel, Field, HttpUrl
|
||||
|
||||
@ -29,6 +38,19 @@ class APIResponse(BaseModel):
|
||||
json_content: Optional[Any] = None # Parsed JSON content if applicable
|
||||
elapsed_time: float # in seconds
|
||||
|
||||
class APICallDetail(BaseModel):
|
||||
"""Model to store detailed information about a single API call for logging."""
|
||||
request_method: str
|
||||
request_url: str
|
||||
request_headers: Dict[str, str]
|
||||
request_params: Optional[Dict[str, Any]] = None
|
||||
request_body: Optional[Any] = None
|
||||
curl_command: str
|
||||
response_status_code: int
|
||||
response_headers: Dict[str, str]
|
||||
response_body: Optional[Any] = None # Could be str for non-JSON, or parsed JSON
|
||||
response_elapsed_time: float
|
||||
|
||||
class APICaller:
|
||||
"""
|
||||
Responsible for executing HTTP/S API calls to the DDMS services.
|
||||
@ -37,8 +59,52 @@ class APICaller:
|
||||
def __init__(self, default_timeout: int = 30, default_headers: Optional[Dict[str, str]] = None):
|
||||
self.default_timeout = default_timeout
|
||||
self.default_headers = default_headers or {}
|
||||
# Session object can be initialized here if we want to reuse it across calls,
|
||||
# but for curlify, a temporary one in _generate_curl_command is fine.
|
||||
self._session_for_curlify = requests.Session()
|
||||
|
||||
def call_api(self, request_data: APIRequest) -> APIResponse:
|
||||
def _generate_curl_command(self, request_data: APIRequest, actual_headers: Dict[str, str]) -> str:
|
||||
"""Generates an equivalent cURL command using the curlify library."""
|
||||
if curlify is None:
|
||||
# Fallback or error message if curlify is not installed
|
||||
# For now, returning a simple message. Ideally, log this.
|
||||
print("ERROR: curlify library is not installed. Cannot generate cURL command.")
|
||||
return "curlify_not_installed"
|
||||
|
||||
# Construct the full URL with parameters for the Request object
|
||||
url_with_params = str(request_data.url)
|
||||
if request_data.params:
|
||||
query_string = urllib.parse.urlencode(request_data.params)
|
||||
url_with_params = f"{url_with_params}?{query_string}"
|
||||
|
||||
# Create a requests.Request object
|
||||
# Note: requests.Request takes 'data' for form data and 'json' for json body.
|
||||
# Our APIRequest has json_data (preferred) and data.
|
||||
req = requests.Request(
|
||||
method=request_data.method.upper(),
|
||||
url=url_with_params,
|
||||
headers=actual_headers, # actual_headers already includes defaults + request-specific
|
||||
data=request_data.data, # Pass form data if present
|
||||
json=request_data.json_data # Pass json data if present (requests handles one or the other)
|
||||
)
|
||||
|
||||
# Prepare the request using a session (needed by curlify)
|
||||
# Using the session from the APICaller instance
|
||||
prepared_request = self._session_for_curlify.prepare_request(req)
|
||||
|
||||
try:
|
||||
# Generate cURL command using curlify
|
||||
# Adding verify=False to match current requests.request(verify=False) behavior
|
||||
# compressed=True by default in curlify, which is usually fine (adds --compressed)
|
||||
curl_command_str = curlify.to_curl(prepared_request, verify=False)
|
||||
print(f"DEBUG: curlify generated command (raw): {curl_command_str}") # Debug print
|
||||
print(f"DEBUG: curlify generated command (repr): {repr(curl_command_str)}") # Added repr print
|
||||
return curl_command_str
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to generate cURL command with curlify: {e}")
|
||||
return f"curlify_generation_failed: {e}"
|
||||
|
||||
def call_api(self, request_data: APIRequest) -> Tuple[APIResponse, APICallDetail]: # Modified return type
|
||||
"""
|
||||
Makes an API call based on the provided request data.
|
||||
|
||||
@ -46,15 +112,24 @@ class APICaller:
|
||||
request_data: An APIRequest Pydantic model instance.
|
||||
|
||||
Returns:
|
||||
An APIResponse Pydantic model instance.
|
||||
A tuple containing:
|
||||
- An APIResponse Pydantic model instance.
|
||||
- An APICallDetail Pydantic model instance.
|
||||
"""
|
||||
merged_headers = {**self.default_headers, **(request_data.headers or {})}
|
||||
timeout = request_data.timeout or self.default_timeout
|
||||
json_payload = request_data.json_data
|
||||
|
||||
# Generate cURL command before making the request
|
||||
curl_command = self._generate_curl_command(request_data, merged_headers)
|
||||
|
||||
request_body_for_log = None
|
||||
if json_payload is not None:
|
||||
request_body_for_log = json_payload
|
||||
elif request_data.data is not None:
|
||||
request_body_for_log = request_data.data # Store as is, might be dict or str
|
||||
|
||||
try:
|
||||
# 如果提供了 body,使用它作为 json 参数
|
||||
json_payload = request_data.json_data
|
||||
|
||||
response = requests.request(
|
||||
method=request_data.method.upper(),
|
||||
url=str(request_data.url),
|
||||
@ -63,47 +138,112 @@ class APICaller:
|
||||
json=json_payload,
|
||||
data=request_data.data,
|
||||
timeout=timeout,
|
||||
verify=False
|
||||
verify=False # As per original code
|
||||
)
|
||||
|
||||
# 不立即引发异常,而是捕获状态码
|
||||
status_code = response.status_code
|
||||
|
||||
json_content = None
|
||||
try:
|
||||
if response.headers.get('Content-Type', '').startswith('application/json'):
|
||||
json_content = response.json()
|
||||
except requests.exceptions.JSONDecodeError:
|
||||
# Not a JSON response or invalid JSON, that's fine for some cases.
|
||||
pass
|
||||
|
||||
return APIResponse(
|
||||
status_code = response.status_code
|
||||
response_headers_dict = dict(response.headers)
|
||||
response_content_bytes = response.content
|
||||
response_elapsed_time = response.elapsed.total_seconds()
|
||||
|
||||
parsed_json_content = None
|
||||
response_body_for_log: Any = response_content_bytes.decode('utf-8', errors='replace') # Default to decoded string
|
||||
|
||||
try:
|
||||
if response_headers_dict.get('Content-Type', '').startswith('application/json'):
|
||||
parsed_json_content = response.json()
|
||||
response_body_for_log = parsed_json_content # If JSON, log the parsed JSON
|
||||
except requests.exceptions.JSONDecodeError:
|
||||
pass # Keep response_body_for_log as decoded string
|
||||
|
||||
api_response = APIResponse(
|
||||
status_code=status_code,
|
||||
headers=dict(response.headers),
|
||||
content=response.content,
|
||||
json_content=json_content,
|
||||
elapsed_time=response.elapsed.total_seconds()
|
||||
headers=response_headers_dict,
|
||||
content=response_content_bytes,
|
||||
json_content=parsed_json_content,
|
||||
elapsed_time=response_elapsed_time
|
||||
)
|
||||
|
||||
api_call_detail = APICallDetail(
|
||||
request_method=request_data.method.upper(),
|
||||
request_url=str(request_data.url),
|
||||
request_headers=merged_headers,
|
||||
request_params=request_data.params,
|
||||
request_body=request_body_for_log,
|
||||
curl_command=curl_command,
|
||||
response_status_code=status_code,
|
||||
response_headers=response_headers_dict,
|
||||
response_body=response_body_for_log,
|
||||
response_elapsed_time=response_elapsed_time
|
||||
)
|
||||
return api_response, api_call_detail
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
# 处理 HTTP 错误
|
||||
print(f"API call to {request_data.url} failed: {e}")
|
||||
return APIResponse(
|
||||
status_code=e.response.status_code,
|
||||
headers=dict(e.response.headers),
|
||||
content=e.response.content,
|
||||
json_content=None,
|
||||
elapsed_time=e.response.elapsed.total_seconds()
|
||||
# For HTTPError, response object should exist
|
||||
status_code = e.response.status_code if e.response else 500
|
||||
response_headers_dict = dict(e.response.headers) if e.response else {}
|
||||
response_content_bytes = e.response.content if e.response else str(e).encode()
|
||||
response_elapsed_time = e.response.elapsed.total_seconds() if e.response and hasattr(e.response, 'elapsed') else 0
|
||||
|
||||
response_body_for_log = response_content_bytes.decode('utf-8', errors='replace')
|
||||
# Try to parse JSON from error response if possible, for logging
|
||||
parsed_json_content_error = None
|
||||
if response_headers_dict.get('Content-Type', '').startswith('application/json'):
|
||||
try:
|
||||
parsed_json_content_error = json.loads(response_body_for_log) # use json.loads for string
|
||||
response_body_for_log = parsed_json_content_error
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
|
||||
api_response_err = APIResponse(
|
||||
status_code=status_code,
|
||||
headers=response_headers_dict,
|
||||
content=response_content_bytes,
|
||||
json_content=parsed_json_content_error, # Log parsed JSON if available
|
||||
elapsed_time=response_elapsed_time
|
||||
)
|
||||
api_call_detail_err = APICallDetail(
|
||||
request_method=request_data.method.upper(),
|
||||
request_url=str(request_data.url),
|
||||
request_headers=merged_headers,
|
||||
request_params=request_data.params,
|
||||
request_body=request_body_for_log, # request body for log
|
||||
curl_command=curl_command,
|
||||
response_status_code=status_code,
|
||||
response_headers=response_headers_dict,
|
||||
response_body=response_body_for_log, # Log decoded string or parsed JSON
|
||||
response_elapsed_time=response_elapsed_time
|
||||
)
|
||||
return api_response_err, api_call_detail_err
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
# 处理其他请求异常
|
||||
print(f"API call to {request_data.url} failed: {e}")
|
||||
return APIResponse(
|
||||
status_code=getattr(e.response, 'status_code', 500),
|
||||
headers=dict(getattr(e.response, 'headers', {})),
|
||||
content=str(e).encode(),
|
||||
# For other RequestExceptions, e.response might not exist or be partial
|
||||
status_code = getattr(e.response, 'status_code', 503) # 503 Service Unavailable seems fitting
|
||||
response_headers_dict = dict(getattr(e.response, 'headers', {}))
|
||||
response_content_str = str(e)
|
||||
response_content_bytes = response_content_str.encode()
|
||||
|
||||
api_response_exc = APIResponse(
|
||||
status_code=status_code,
|
||||
headers=response_headers_dict,
|
||||
content=response_content_bytes,
|
||||
json_content=None,
|
||||
elapsed_time=0
|
||||
)
|
||||
api_call_detail_exc = APICallDetail(
|
||||
request_method=request_data.method.upper(),
|
||||
request_url=str(request_data.url),
|
||||
request_headers=merged_headers,
|
||||
request_params=request_data.params,
|
||||
request_body=request_body_for_log, # request body for log
|
||||
curl_command=curl_command,
|
||||
response_status_code=status_code,
|
||||
response_headers=response_headers_dict,
|
||||
response_body=response_content_str, # Log the error string
|
||||
response_elapsed_time=0
|
||||
)
|
||||
return api_response_exc, api_call_detail_exc
|
||||
|
||||
# Example Usage (can be moved to tests or main application logic)
|
||||
if __name__ == '__main__':
|
||||
@ -115,13 +255,15 @@ if __name__ == '__main__':
|
||||
url=HttpUrl("https://jsonplaceholder.typicode.com/todos/1"),
|
||||
headers={"X-Request-ID": "12345"}
|
||||
)
|
||||
response = caller.call_api(get_req_data)
|
||||
response, detail = caller.call_api(get_req_data) # Unpack two values now
|
||||
print("GET Response:")
|
||||
if response.json_content:
|
||||
print(f"Status: {response.status_code}, Data: {response.json_content}")
|
||||
else:
|
||||
print(f"Status: {response.status_code}, Content: {response.content.decode()}")
|
||||
print(f"Time taken: {response.elapsed_time:.4f}s")
|
||||
print("GET Call Detail:")
|
||||
print(detail.model_dump_json(indent=2)) # Use model_dump_json for pretty print
|
||||
|
||||
print("\n")
|
||||
|
||||
@ -132,13 +274,15 @@ if __name__ == '__main__':
|
||||
json_data={"title": "foo", "body": "bar", "userId": 1},
|
||||
headers={"Content-Type": "application/json; charset=UTF-8"}
|
||||
)
|
||||
response = caller.call_api(post_req_data)
|
||||
response, detail = caller.call_api(post_req_data)
|
||||
print("POST Response with json_data:")
|
||||
if response.json_content:
|
||||
print(f"Status: {response.status_code}, Data: {response.json_content}")
|
||||
else:
|
||||
print(f"Status: {response.status_code}, Content: {response.content.decode()}")
|
||||
print(f"Time taken: {response.elapsed_time:.4f}s")
|
||||
print("POST Call Detail (json_data):")
|
||||
print(detail.model_dump_json(indent=2))
|
||||
|
||||
# Example POST request with body (alias for json_data)
|
||||
post_req_data_with_body = APIRequest(
|
||||
@ -147,13 +291,15 @@ if __name__ == '__main__':
|
||||
body={"title": "using body", "body": "testing body alias", "userId": 2},
|
||||
headers={"Content-Type": "application/json; charset=UTF-8"}
|
||||
)
|
||||
response = caller.call_api(post_req_data_with_body)
|
||||
response, detail = caller.call_api(post_req_data_with_body)
|
||||
print("\nPOST Response with body:")
|
||||
if response.json_content:
|
||||
print(f"Status: {response.status_code}, Data: {response.json_content}")
|
||||
else:
|
||||
print(f"Status: {response.status_code}, Content: {response.content.decode()}")
|
||||
print(f"Time taken: {response.elapsed_time:.4f}s")
|
||||
print("POST Call Detail (body):")
|
||||
print(detail.model_dump_json(indent=2))
|
||||
|
||||
# Example list body request (demonstrating array body support)
|
||||
array_req_data = APIRequest(
|
||||
@ -162,20 +308,24 @@ if __name__ == '__main__':
|
||||
body=["test_string", "another_value"],
|
||||
headers={"Content-Type": "application/json; charset=UTF-8"}
|
||||
)
|
||||
response = caller.call_api(array_req_data)
|
||||
response, detail = caller.call_api(array_req_data)
|
||||
print("\nPOST Response with array body:")
|
||||
if response.json_content:
|
||||
print(f"Status: {response.status_code}, Data: {response.json_content}")
|
||||
else:
|
||||
print(f"Status: {response.status_code}, Content: {response.content.decode()}")
|
||||
print(f"Time taken: {response.elapsed_time:.4f}s")
|
||||
print("POST Call Detail (array body):")
|
||||
print(detail.model_dump_json(indent=2))
|
||||
|
||||
# Example Error request (non-existent domain)
|
||||
error_req_data = APIRequest(
|
||||
method="GET",
|
||||
url=HttpUrl("https://nonexistentdomain.invalid"),
|
||||
)
|
||||
response = caller.call_api(error_req_data)
|
||||
response, detail = caller.call_api(error_req_data)
|
||||
print("\nError GET Response:")
|
||||
print(f"Status: {response.status_code}, Content: {response.content.decode()}")
|
||||
print(f"Time taken: {response.elapsed_time:.4f}s")
|
||||
print(f"Time taken: {response.elapsed_time:.4f}s")
|
||||
print("Error GET Call Detail:")
|
||||
print(detail.model_dump_json(indent=2))
|
||||
@ -7,35 +7,34 @@
|
||||
import logging
|
||||
import json
|
||||
import time
|
||||
import re # 添加 re 模块导入
|
||||
import os # Added os for path operations
|
||||
import re
|
||||
from typing import Dict, List, Any, Optional, Union, Tuple, Type, ForwardRef
|
||||
from enum import Enum
|
||||
import datetime
|
||||
import datetime as dt
|
||||
from uuid import UUID
|
||||
from dataclasses import asdict as dataclass_asdict, is_dataclass # New import
|
||||
from dataclasses import asdict as dataclass_asdict, is_dataclass
|
||||
import copy
|
||||
|
||||
from pydantic import BaseModel, Field, create_model
|
||||
from pydantic import BaseModel, Field, create_model, HttpUrl # Added HttpUrl for Literal type hint if needed
|
||||
from pydantic.networks import EmailStr
|
||||
from pydantic.types import Literal # Explicitly import Literal
|
||||
|
||||
from .input_parser.parser import InputParser, YAPIEndpoint, SwaggerEndpoint, ParsedYAPISpec, ParsedSwaggerSpec
|
||||
from .api_caller.caller import APICaller, APIRequest, APIResponse
|
||||
from .api_caller.caller import APICaller, APIRequest, APIResponse, APICallDetail # Ensure APICallDetail is imported
|
||||
from .json_schema_validator.validator import JSONSchemaValidator
|
||||
from .test_framework_core import ValidationResult, TestSeverity, APIRequestContext, APIResponseContext, BaseAPITestCase
|
||||
from .test_case_registry import TestCaseRegistry
|
||||
# 尝试导入 utils.schema_utils
|
||||
from .utils import schema_utils
|
||||
from .utils.common_utils import format_url_with_path_params # 新增导入
|
||||
from .utils.common_utils import format_url_with_path_params
|
||||
|
||||
# 尝试导入 LLMService,如果失败则允许,因为 LLM 功能是可选的
|
||||
try:
|
||||
from .llm_utils.llm_service import LLMService
|
||||
except ImportError:
|
||||
LLMService = None
|
||||
logging.getLogger(__name__).info("LLMService 未找到,LLM 相关功能将不可用。")
|
||||
|
||||
# Cache for dynamically created Pydantic models to avoid redefinition issues
|
||||
_dynamic_model_cache: Dict[str, Type[BaseModel]] = {}
|
||||
|
||||
class ExecutedTestCaseResult:
|
||||
@ -321,8 +320,9 @@ class TestSummary:
|
||||
print(f" - 验证点: {vp.message}")
|
||||
|
||||
class APITestOrchestrator:
|
||||
"""API测试编排器"""
|
||||
|
||||
"""
|
||||
测试编排器,负责加载API定义、发现和执行测试用例、生成报告等。
|
||||
"""
|
||||
def __init__(self, base_url: str,
|
||||
custom_test_cases_dir: Optional[str] = None,
|
||||
llm_api_key: Optional[str] = None,
|
||||
@ -331,77 +331,53 @@ class APITestOrchestrator:
|
||||
use_llm_for_request_body: bool = False,
|
||||
use_llm_for_path_params: bool = False,
|
||||
use_llm_for_query_params: bool = False,
|
||||
use_llm_for_headers: bool = False
|
||||
use_llm_for_headers: bool = False,
|
||||
output_dir: Optional[str] = None # output_dir is now optional and not used for saving API call details internally
|
||||
):
|
||||
"""
|
||||
初始化API测试编排器
|
||||
|
||||
Args:
|
||||
base_url: API基础URL
|
||||
custom_test_cases_dir: 存放自定义 APITestCase 的目录路径。如果为 None,则不加载自定义测试用例。
|
||||
llm_api_key: 大模型服务的API Key。
|
||||
llm_base_url: 大模型服务的兼容OpenAI的基础URL。
|
||||
llm_model_name: 要使用的具体模型名称。
|
||||
use_llm_for_request_body: 是否全局启用LLM生成请求体。
|
||||
use_llm_for_path_params: 是否全局启用LLM生成路径参数。
|
||||
use_llm_for_query_params: 是否全局启用LLM生成查询参数。
|
||||
use_llm_for_headers: 是否全局启用LLM生成头部参数。
|
||||
"""
|
||||
self.base_url = base_url.rstrip('/')
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
# 初始化组件
|
||||
self.parser = InputParser()
|
||||
self.api_caller = APICaller()
|
||||
self.validator = JSONSchemaValidator() # JSON Schema 验证器,可能会被测试用例内部使用
|
||||
|
||||
self.test_case_registry: Optional[TestCaseRegistry] = None
|
||||
if custom_test_cases_dir:
|
||||
self.logger.info(f"初始化 TestCaseRegistry,扫描目录: {custom_test_cases_dir}")
|
||||
try:
|
||||
self.test_case_registry = TestCaseRegistry(test_cases_dir=custom_test_cases_dir)
|
||||
self.logger.info(f"TestCaseRegistry 初始化完成,发现 {len(self.test_case_registry.get_all_test_case_classes())} 个测试用例类。")
|
||||
except Exception as e:
|
||||
self.logger.error(f"初始化 TestCaseRegistry 失败: {e}", exc_info=True)
|
||||
else:
|
||||
self.logger.info("未提供 custom_test_cases_dir,不加载自定义 APITestCase。")
|
||||
self.schema_validator = JSONSchemaValidator()
|
||||
self.test_case_registry = TestCaseRegistry(custom_test_cases_dir)
|
||||
self.logger = logging.getLogger(__name__)
|
||||
# self.output_dir is kept if other parts of the orchestrator might use it,
|
||||
# but it's no longer used by the removed _save_api_call_details
|
||||
self.output_dir_param = output_dir
|
||||
|
||||
self.api_call_details_log: List[APICallDetail] = []
|
||||
|
||||
# LLM Service Initialization
|
||||
self.llm_service: Optional[LLMService] = None
|
||||
if LLMService and llm_api_key:
|
||||
try:
|
||||
self.llm_service = LLMService(api_key=llm_api_key, base_url=llm_base_url, model_name=llm_model_name)
|
||||
self.logger.info(f"LLMService initialized successfully with model: {llm_model_name or 'default'}.")
|
||||
except Exception as e:
|
||||
self.logger.error(f"LLMService initialization failed: {e}. LLM features will be disabled.", exc_info=True)
|
||||
self.llm_service = None
|
||||
elif LLMService and not llm_api_key:
|
||||
self.logger.info("LLMService is available, but LLM API key was not provided. LLM features will be disabled.")
|
||||
|
||||
# LLM 全局配置开关
|
||||
self.use_llm_for_request_body = use_llm_for_request_body
|
||||
self.use_llm_for_path_params = use_llm_for_path_params
|
||||
self.use_llm_for_query_params = use_llm_for_query_params
|
||||
self.use_llm_for_headers = use_llm_for_headers
|
||||
|
||||
self.llm_service: Optional[LLMService] = None
|
||||
if LLMService is None:
|
||||
self.logger.warning("LLMService 类未能导入,LLM 相关功能将完全禁用。")
|
||||
# 强制所有LLM使用为False,并确保服务实例为None
|
||||
self.llm_endpoint_params_cache: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
if (self.use_llm_for_request_body or \
|
||||
self.use_llm_for_path_params or \
|
||||
self.use_llm_for_query_params or \
|
||||
self.use_llm_for_headers) and not self.llm_service:
|
||||
self.logger.warning("LLM-based data generation was enabled, but LLMService is not available or failed to initialize. Disabling all LLM features.")
|
||||
self.use_llm_for_request_body = False
|
||||
self.use_llm_for_path_params = False
|
||||
self.use_llm_for_query_params = False
|
||||
self.use_llm_for_headers = False
|
||||
elif llm_api_key and llm_base_url and llm_model_name: # 直接检查配置是否完整
|
||||
try:
|
||||
self.llm_service = LLMService(
|
||||
api_key=llm_api_key,
|
||||
base_url=llm_base_url,
|
||||
model_name=llm_model_name
|
||||
)
|
||||
self.logger.info(f"LLMService 已成功初始化,模型: {llm_model_name}。")
|
||||
except ValueError as ve:
|
||||
self.logger.error(f"LLMService 初始化失败 (参数错误): {ve}。LLM相关功能将不可用。")
|
||||
self.llm_service = None # 确保初始化失败时服务为None
|
||||
except Exception as e:
|
||||
self.logger.error(f"LLMService 初始化时发生未知错误: {e}。LLM相关功能将不可用。", exc_info=True)
|
||||
self.llm_service = None # 确保初始化失败时服务为None
|
||||
else:
|
||||
# 如果LLMService类存在,但配置不完整
|
||||
if LLMService:
|
||||
self.logger.warning("LLMService 类已找到,但未提供完整的LLM配置 (api_key, base_url, model_name)。LLM相关功能将不可用。")
|
||||
# self.llm_service 默认就是 None,无需额外操作
|
||||
|
||||
# 新增:端点级别的LLM生成参数缓存
|
||||
self.llm_endpoint_params_cache: Dict[str, Dict[str, Any]] = {}
|
||||
def get_api_call_details(self) -> List[APICallDetail]:
|
||||
"""Returns the collected list of API call details."""
|
||||
return self.api_call_details_log
|
||||
|
||||
def _should_use_llm_for_param_type(
|
||||
self,
|
||||
@ -868,7 +844,7 @@ class APITestOrchestrator:
|
||||
test_case_instance = test_case_class(
|
||||
endpoint_spec=endpoint_spec_dict,
|
||||
global_api_spec=global_spec_dict,
|
||||
json_schema_validator=self.validator,
|
||||
json_schema_validator=self.schema_validator,
|
||||
llm_service=self.llm_service # Pass the orchestrator's LLM service instance
|
||||
)
|
||||
self.logger.info(f"开始执行测试用例 '{test_case_instance.id}' ({test_case_instance.name}) for endpoint '{endpoint_spec_dict.get('method', 'N/A')} {endpoint_spec_dict.get('path', 'N/A')}'")
|
||||
@ -966,32 +942,34 @@ class APITestOrchestrator:
|
||||
)
|
||||
|
||||
response_call_start_time = time.time()
|
||||
api_response_obj = self.api_caller.call_api(api_request_obj)
|
||||
# api_response_obj = self.api_caller.call_api(api_request_obj)
|
||||
api_response, api_call_detail = self.api_caller.call_api(api_request_obj)
|
||||
self.api_call_details_log.append(api_call_detail) # 记录日志
|
||||
|
||||
response_call_elapsed_time = time.time() - response_call_start_time
|
||||
|
||||
actual_text_content: Optional[str] = None
|
||||
if hasattr(api_response_obj, 'text_content') and api_response_obj.text_content is not None:
|
||||
actual_text_content = api_response_obj.text_content
|
||||
elif api_response_obj.json_content is not None:
|
||||
if isinstance(api_response_obj.json_content, str): # Should not happen if json_content is parsed
|
||||
actual_text_content = api_response_obj.json_content
|
||||
# 使用解包后的 api_response:
|
||||
if hasattr(api_response, 'text_content') and api_response.text_content is not None:
|
||||
actual_text_content = api_response.text_content
|
||||
elif api_response.json_content is not None: # <--- 使用 api_response
|
||||
if isinstance(api_response.json_content, str):
|
||||
actual_text_content = api_response.json_content
|
||||
else:
|
||||
try:
|
||||
actual_text_content = json.dumps(api_response_obj.json_content, ensure_ascii=False)
|
||||
except TypeError: # If json_content is not serializable (e.g. bytes)
|
||||
actual_text_content = str(api_response_obj.json_content)
|
||||
|
||||
actual_text_content = json.dumps(api_response.json_content, ensure_ascii=False)
|
||||
except TypeError:
|
||||
actual_text_content = str(api_response.json_content)
|
||||
|
||||
api_response_context = APIResponseContext(
|
||||
status_code=api_response_obj.status_code,
|
||||
headers=api_response_obj.headers,
|
||||
json_content=api_response_obj.json_content,
|
||||
status_code=api_response.status_code, # <--- 使用 api_response
|
||||
headers=api_response.headers, # <--- 使用 api_response
|
||||
json_content=api_response.json_content, # <--- 使用 api_response
|
||||
text_content=actual_text_content,
|
||||
elapsed_time=response_call_elapsed_time,
|
||||
original_response= getattr(api_response_obj, 'raw_response', None), # Pass raw if available
|
||||
original_response= getattr(api_response, 'raw_response', None), # <--- 使用 api_response
|
||||
request_context=api_request_context
|
||||
)
|
||||
|
||||
validation_results.extend(test_case_instance.validate_response(api_response_context, api_request_context))
|
||||
validation_results.extend(test_case_instance.check_performance(api_response_context, api_request_context))
|
||||
|
||||
@ -1424,12 +1402,15 @@ class APITestOrchestrator:
|
||||
self.logger.error(f"从 run_tests_from_yapi 重新初始化 TestCaseRegistry 失败: {e}", exc_info=True)
|
||||
|
||||
self.logger.info(f"从YAPI文件加载API定义: {yapi_file_path}")
|
||||
parsed_yapi = self.parser.parse_yapi_spec(yapi_file_path)
|
||||
self.api_call_details_log = [] # Reset for new run
|
||||
|
||||
parsed_yapi = self.parser.parse_yapi_spec(yapi_file_path) # Corrected: self.parser
|
||||
summary = TestSummary()
|
||||
|
||||
if not parsed_yapi:
|
||||
self.logger.error(f"解析YAPI文件失败: {yapi_file_path}")
|
||||
summary.finalize_summary()
|
||||
# No longer calls _save_api_call_details here
|
||||
return summary
|
||||
|
||||
endpoints_to_test = parsed_yapi.endpoints
|
||||
@ -1453,6 +1434,8 @@ class APITestOrchestrator:
|
||||
summary.add_endpoint_result(result)
|
||||
|
||||
summary.finalize_summary()
|
||||
# No longer calls _save_api_call_details here
|
||||
summary.print_summary_to_console() # Keep console print
|
||||
return summary
|
||||
|
||||
def run_tests_from_swagger(self, swagger_file_path: str,
|
||||
@ -1468,12 +1451,15 @@ class APITestOrchestrator:
|
||||
self.logger.error(f"从 run_tests_from_swagger 重新初始化 TestCaseRegistry 失败: {e}", exc_info=True)
|
||||
|
||||
self.logger.info(f"从Swagger文件加载API定义: {swagger_file_path}")
|
||||
parsed_swagger = self.parser.parse_swagger_spec(swagger_file_path)
|
||||
self.api_call_details_log = [] # Reset for new run
|
||||
|
||||
parsed_swagger = self.parser.parse_swagger_spec(swagger_file_path) # Corrected: self.parser
|
||||
summary = TestSummary()
|
||||
|
||||
if not parsed_swagger:
|
||||
self.logger.error(f"解析Swagger文件失败: {swagger_file_path}")
|
||||
summary.finalize_summary()
|
||||
# No longer calls _save_api_call_details here
|
||||
return summary
|
||||
|
||||
endpoints_to_test = parsed_swagger.endpoints
|
||||
@ -1481,7 +1467,7 @@ class APITestOrchestrator:
|
||||
endpoints_to_test = [ep for ep in endpoints_to_test if any(tag in ep.tags for tag in tags)]
|
||||
|
||||
summary.set_total_endpoints_defined(len(endpoints_to_test))
|
||||
|
||||
|
||||
total_applicable_tcs = 0
|
||||
if self.test_case_registry:
|
||||
for endpoint_spec in endpoints_to_test:
|
||||
@ -1497,6 +1483,8 @@ class APITestOrchestrator:
|
||||
summary.add_endpoint_result(result)
|
||||
|
||||
summary.finalize_summary()
|
||||
# No longer calls _save_api_call_details here
|
||||
summary.print_summary_to_console() # Keep console print
|
||||
return summary
|
||||
|
||||
def _generate_data_from_schema(self, schema: Dict[str, Any],
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
{
|
||||
"summary_metadata": {
|
||||
"start_time": "2025-05-28T13:21:26.202861",
|
||||
"end_time": "2025-05-28T13:21:28.430608",
|
||||
"duration_seconds": "2.23"
|
||||
"start_time": "2025-05-28T16:49:42.000096",
|
||||
"end_time": "2025-05-28T16:49:43.454316",
|
||||
"duration_seconds": "1.45"
|
||||
},
|
||||
"endpoint_stats": {
|
||||
"total_defined": 6,
|
||||
@ -28,9 +28,9 @@
|
||||
"endpoint_id": "POST /api/dms/{dms_instance_code}/v1/message/push/{schema}/{version}",
|
||||
"endpoint_name": "数据推送接口",
|
||||
"overall_status": "失败",
|
||||
"duration_seconds": 0.959088,
|
||||
"start_time": "2025-05-28T13:21:26.203352",
|
||||
"end_time": "2025-05-28T13:21:27.162440",
|
||||
"duration_seconds": 0.595545,
|
||||
"start_time": "2025-05-28T16:49:42.000447",
|
||||
"end_time": "2025-05-28T16:49:42.595992",
|
||||
"executed_test_cases": [
|
||||
{
|
||||
"test_case_id": "TC-STATUS-001",
|
||||
@ -38,8 +38,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "通过",
|
||||
"message": "响应状态码为 200,符合预期 200。",
|
||||
"duration_seconds": 0.48625695798546076,
|
||||
"timestamp": "2025-05-28T13:21:26.689735",
|
||||
"duration_seconds": 0.3856248748488724,
|
||||
"timestamp": "2025-05-28T16:49:42.386165",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -53,8 +53,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "通过",
|
||||
"message": "Schema验证步骤完成(未发现问题,或schema不适用/未为此响应定义)。",
|
||||
"duration_seconds": 0.09794595907442272,
|
||||
"timestamp": "2025-05-28T13:21:26.787799",
|
||||
"duration_seconds": 0.04244487499818206,
|
||||
"timestamp": "2025-05-28T16:49:42.428768",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -68,8 +68,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "失败",
|
||||
"message": "API通过HTTP (http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/message/push/example_schema/example_version) 响应了成功的状态码 200,这违反了HTTPS强制策略。",
|
||||
"duration_seconds": 0.12197162513621151,
|
||||
"timestamp": "2025-05-28T13:21:26.909942",
|
||||
"duration_seconds": 0.028596166986972094,
|
||||
"timestamp": "2025-05-28T16:49:42.457422",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200
|
||||
@ -82,8 +82,8 @@
|
||||
"test_case_severity": "中",
|
||||
"status": "通过",
|
||||
"message": "跳过测试:在查询参数中未找到合适的字段来测试类型不匹配。",
|
||||
"duration_seconds": 0.06489037512801588,
|
||||
"timestamp": "2025-05-28T13:21:26.975534",
|
||||
"duration_seconds": 0.03503883397206664,
|
||||
"timestamp": "2025-05-28T16:49:42.492537",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -96,28 +96,35 @@
|
||||
"test_case_name": "Error Code 4001 - Request Body Type Mismatch Validation",
|
||||
"test_case_severity": "中",
|
||||
"status": "失败",
|
||||
"message": "当请求体字段 'isSearchCount' 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '88'.",
|
||||
"duration_seconds": 0.04103666706942022,
|
||||
"timestamp": "2025-05-28T13:21:27.016732",
|
||||
"message": "当请求体字段 'isSearchCount' 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '39'.",
|
||||
"duration_seconds": 0.04519999981857836,
|
||||
"timestamp": "2025-05-28T16:49:42.537838",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200,
|
||||
"response_body": {
|
||||
"code": 88,
|
||||
"message": "est sit aute occaecat",
|
||||
"code": 39,
|
||||
"message": "tempor",
|
||||
"data": {
|
||||
"total": 35,
|
||||
"total": 61,
|
||||
"list": [
|
||||
{
|
||||
"dsid": "94",
|
||||
"dataRegion": "laborum non ullamco",
|
||||
"dsid": "34",
|
||||
"dataRegion": "anim incididunt veniam consequat exercitation",
|
||||
"gasReleaseMon": null,
|
||||
"gasReleaseYear": null,
|
||||
"releaseGasCum": null
|
||||
},
|
||||
{
|
||||
"dsid": "100",
|
||||
"dataRegion": "culpa quis ipsum commodo",
|
||||
"dsid": "35",
|
||||
"dataRegion": "ex",
|
||||
"gasReleaseMon": null,
|
||||
"gasReleaseYear": null,
|
||||
"releaseGasCum": null
|
||||
},
|
||||
{
|
||||
"dsid": "4",
|
||||
"dataRegion": "ullamco et",
|
||||
"gasReleaseMon": null,
|
||||
"gasReleaseYear": null,
|
||||
"releaseGasCum": null
|
||||
@ -140,8 +147,8 @@
|
||||
"test_case_severity": "高",
|
||||
"status": "通过",
|
||||
"message": "跳过测试:在API规范中未找到合适的必填请求体字段用于移除测试。",
|
||||
"duration_seconds": 0.049191041849553585,
|
||||
"timestamp": "2025-05-28T13:21:27.066013",
|
||||
"duration_seconds": 0.027589458972215652,
|
||||
"timestamp": "2025-05-28T16:49:42.565513",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -155,8 +162,8 @@
|
||||
"test_case_severity": "高",
|
||||
"status": "通过",
|
||||
"message": "跳过测试:在API规范中未找到合适的必填查询参数用于移除测试。",
|
||||
"duration_seconds": 0.09616558300331235,
|
||||
"timestamp": "2025-05-28T13:21:27.162281",
|
||||
"duration_seconds": 0.030305457999929786,
|
||||
"timestamp": "2025-05-28T16:49:42.595953",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -170,9 +177,9 @@
|
||||
"endpoint_id": "POST /api/dms/{dms_instance_code}/v1/cd_geo_unit/{version}",
|
||||
"endpoint_name": "地质单元列表查询",
|
||||
"overall_status": "失败",
|
||||
"duration_seconds": 0.432745,
|
||||
"start_time": "2025-05-28T13:21:27.162671",
|
||||
"end_time": "2025-05-28T13:21:27.595416",
|
||||
"duration_seconds": 0.233222,
|
||||
"start_time": "2025-05-28T16:49:42.596025",
|
||||
"end_time": "2025-05-28T16:49:42.829247",
|
||||
"executed_test_cases": [
|
||||
{
|
||||
"test_case_id": "TC-STATUS-001",
|
||||
@ -180,8 +187,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "通过",
|
||||
"message": "响应状态码为 200,符合预期 200。",
|
||||
"duration_seconds": 0.0457927908282727,
|
||||
"timestamp": "2025-05-28T13:21:27.208720",
|
||||
"duration_seconds": 0.02231612498871982,
|
||||
"timestamp": "2025-05-28T16:49:42.618421",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -195,8 +202,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "通过",
|
||||
"message": "Schema验证步骤完成(未发现问题,或schema不适用/未为此响应定义)。",
|
||||
"duration_seconds": 0.03356829099357128,
|
||||
"timestamp": "2025-05-28T13:21:27.242403",
|
||||
"duration_seconds": 0.0266630828846246,
|
||||
"timestamp": "2025-05-28T16:49:42.645136",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -210,8 +217,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "失败",
|
||||
"message": "API通过HTTP (http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0) 响应了成功的状态码 200,这违反了HTTPS强制策略。",
|
||||
"duration_seconds": 0.16332429205067456,
|
||||
"timestamp": "2025-05-28T13:21:27.406060",
|
||||
"duration_seconds": 0.06599487503990531,
|
||||
"timestamp": "2025-05-28T16:49:42.711186",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200
|
||||
@ -223,35 +230,21 @@
|
||||
"test_case_name": "Error Code 4001 - Query Parameter Type Mismatch Validation",
|
||||
"test_case_severity": "中",
|
||||
"status": "失败",
|
||||
"message": "当查询参数 'pageNo' (路径: 'pageNo') 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '30'.",
|
||||
"duration_seconds": 0.054804832907393575,
|
||||
"timestamp": "2025-05-28T13:21:27.461171",
|
||||
"message": "当查询参数 'pageNo' (路径: 'pageNo') 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '51'.",
|
||||
"duration_seconds": 0.03108358313329518,
|
||||
"timestamp": "2025-05-28T16:49:42.742351",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200,
|
||||
"response_body": {
|
||||
"code": 30,
|
||||
"message": "dolore incididunt nulla dolor",
|
||||
"code": 51,
|
||||
"message": "esse sit est pariatur quis",
|
||||
"data": {
|
||||
"total": 67,
|
||||
"total": 7,
|
||||
"list": [
|
||||
{
|
||||
"dsid": "1",
|
||||
"dataRegion": "occaecat",
|
||||
"gasReleaseMon": null,
|
||||
"gasReleaseYear": null,
|
||||
"releaseGasCum": null
|
||||
},
|
||||
{
|
||||
"dsid": "29",
|
||||
"dataRegion": "ullamco adipisicing velit Excepteur aliquip",
|
||||
"gasReleaseMon": null,
|
||||
"gasReleaseYear": null,
|
||||
"releaseGasCum": null
|
||||
},
|
||||
{
|
||||
"dsid": "97",
|
||||
"dataRegion": "commodo",
|
||||
"dsid": "52",
|
||||
"dataRegion": "eu nostrud",
|
||||
"gasReleaseMon": null,
|
||||
"gasReleaseYear": null,
|
||||
"releaseGasCum": null
|
||||
@ -273,35 +266,21 @@
|
||||
"test_case_name": "Error Code 4001 - Request Body Type Mismatch Validation",
|
||||
"test_case_severity": "中",
|
||||
"status": "失败",
|
||||
"message": "当请求体字段 'isSearchCount' 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '13'.",
|
||||
"duration_seconds": 0.05122020794078708,
|
||||
"timestamp": "2025-05-28T13:21:27.512585",
|
||||
"message": "当请求体字段 'isSearchCount' 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '27'.",
|
||||
"duration_seconds": 0.02375933318398893,
|
||||
"timestamp": "2025-05-28T16:49:42.766159",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200,
|
||||
"response_body": {
|
||||
"code": 13,
|
||||
"message": "voluptate sint culpa",
|
||||
"code": 27,
|
||||
"message": "ipsum",
|
||||
"data": {
|
||||
"total": 35,
|
||||
"total": 4,
|
||||
"list": [
|
||||
{
|
||||
"dsid": "86",
|
||||
"dataRegion": "laborum",
|
||||
"gasReleaseMon": null,
|
||||
"gasReleaseYear": null,
|
||||
"releaseGasCum": null
|
||||
},
|
||||
{
|
||||
"dsid": "53",
|
||||
"dataRegion": "nisi ipsum",
|
||||
"gasReleaseMon": null,
|
||||
"gasReleaseYear": null,
|
||||
"releaseGasCum": null
|
||||
},
|
||||
{
|
||||
"dsid": "78",
|
||||
"dataRegion": "nostrud",
|
||||
"dsid": "37",
|
||||
"dataRegion": "enim officia velit aliqua exercitation",
|
||||
"gasReleaseMon": null,
|
||||
"gasReleaseYear": null,
|
||||
"releaseGasCum": null
|
||||
@ -324,8 +303,8 @@
|
||||
"test_case_severity": "高",
|
||||
"status": "通过",
|
||||
"message": "跳过测试:在API规范中未找到合适的必填请求体字段用于移除测试。",
|
||||
"duration_seconds": 0.04973745811730623,
|
||||
"timestamp": "2025-05-28T13:21:27.562566",
|
||||
"duration_seconds": 0.03237150004133582,
|
||||
"timestamp": "2025-05-28T16:49:42.798618",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -339,8 +318,8 @@
|
||||
"test_case_severity": "高",
|
||||
"status": "通过",
|
||||
"message": "跳过测试:在API规范中未找到合适的必填查询参数用于移除测试。",
|
||||
"duration_seconds": 0.03264466719701886,
|
||||
"timestamp": "2025-05-28T13:21:27.595324",
|
||||
"duration_seconds": 0.030383124947547913,
|
||||
"timestamp": "2025-05-28T16:49:42.829203",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -354,9 +333,9 @@
|
||||
"endpoint_id": "PUT /api/dms/{dms_instance_code}/v1/cd_geo_unit",
|
||||
"endpoint_name": "地质单元数据修改",
|
||||
"overall_status": "失败",
|
||||
"duration_seconds": 0.227002,
|
||||
"start_time": "2025-05-28T13:21:27.595483",
|
||||
"end_time": "2025-05-28T13:21:27.822485",
|
||||
"duration_seconds": 0.125734,
|
||||
"start_time": "2025-05-28T16:49:42.829278",
|
||||
"end_time": "2025-05-28T16:49:42.955012",
|
||||
"executed_test_cases": [
|
||||
{
|
||||
"test_case_id": "TC-STATUS-001",
|
||||
@ -364,8 +343,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "通过",
|
||||
"message": "响应状态码为 200,符合预期 200。",
|
||||
"duration_seconds": 0.03572654211893678,
|
||||
"timestamp": "2025-05-28T13:21:27.631383",
|
||||
"duration_seconds": 0.022012750152498484,
|
||||
"timestamp": "2025-05-28T16:49:42.851563",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -379,8 +358,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "通过",
|
||||
"message": "针对 PUT http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit (状态码 200) 的响应体 conforms to the JSON schema.",
|
||||
"duration_seconds": 0.03160024993121624,
|
||||
"timestamp": "2025-05-28T13:21:27.663070",
|
||||
"duration_seconds": 0.019489916041493416,
|
||||
"timestamp": "2025-05-28T16:49:42.871101",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -394,8 +373,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "失败",
|
||||
"message": "API通过HTTP (http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit) 响应了成功的状态码 200,这违反了HTTPS强制策略。",
|
||||
"duration_seconds": 0.025042208144441247,
|
||||
"timestamp": "2025-05-28T13:21:27.688232",
|
||||
"duration_seconds": 0.016527208033949137,
|
||||
"timestamp": "2025-05-28T16:49:42.887700",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200
|
||||
@ -407,16 +386,16 @@
|
||||
"test_case_name": "Error Code 4001 - Query Parameter Type Mismatch Validation",
|
||||
"test_case_severity": "中",
|
||||
"status": "失败",
|
||||
"message": "当查询参数 'id' (路径: 'id') 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '36'.",
|
||||
"duration_seconds": 0.028811916010454297,
|
||||
"timestamp": "2025-05-28T13:21:27.717123",
|
||||
"message": "当查询参数 'id' (路径: 'id') 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '93'.",
|
||||
"duration_seconds": 0.021497624926269054,
|
||||
"timestamp": "2025-05-28T16:49:42.909245",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200,
|
||||
"response_body": {
|
||||
"code": 36,
|
||||
"message": "Lorem",
|
||||
"data": true
|
||||
"code": 93,
|
||||
"message": "Duis officia",
|
||||
"data": false
|
||||
},
|
||||
"expected_http_status_codes": [
|
||||
400,
|
||||
@ -432,16 +411,16 @@
|
||||
"test_case_name": "Error Code 4001 - Request Body Type Mismatch Validation",
|
||||
"test_case_severity": "中",
|
||||
"status": "失败",
|
||||
"message": "当请求体字段 'id' 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '34'.",
|
||||
"duration_seconds": 0.02846591593697667,
|
||||
"timestamp": "2025-05-28T13:21:27.745660",
|
||||
"message": "当请求体字段 'id' 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '97'.",
|
||||
"duration_seconds": 0.014971292112022638,
|
||||
"timestamp": "2025-05-28T16:49:42.924262",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200,
|
||||
"response_body": {
|
||||
"code": 34,
|
||||
"message": "commodo elit quis adipisicing",
|
||||
"data": false
|
||||
"code": 97,
|
||||
"message": "ea proident",
|
||||
"data": true
|
||||
},
|
||||
"expected_http_status_codes": [
|
||||
400,
|
||||
@ -458,8 +437,8 @@
|
||||
"test_case_severity": "高",
|
||||
"status": "通过",
|
||||
"message": "当移除必填请求体字段 'id' 时,API响应了状态码 200 (非主要预期HTTP状态 [400, 422],但为4xx客户端错误), 且响应体中包含预期的业务错误码 '4003' (字段: 'code').",
|
||||
"duration_seconds": 0.042632041964679956,
|
||||
"timestamp": "2025-05-28T13:21:27.788370",
|
||||
"duration_seconds": 0.016076958971098065,
|
||||
"timestamp": "2025-05-28T16:49:42.940383",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -473,14 +452,14 @@
|
||||
"test_case_severity": "高",
|
||||
"status": "失败",
|
||||
"message": "当移除必填查询参数 'id' 时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4003'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '34'.",
|
||||
"duration_seconds": 0.03391150012612343,
|
||||
"timestamp": "2025-05-28T13:21:27.822382",
|
||||
"duration_seconds": 0.014534207992255688,
|
||||
"timestamp": "2025-05-28T16:49:42.954972",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200,
|
||||
"response_body": {
|
||||
"code": 34,
|
||||
"message": "occaecat qui mollit",
|
||||
"message": "commodo consequat velit veniam",
|
||||
"data": true
|
||||
},
|
||||
"expected_http_status_codes": [
|
||||
@ -498,9 +477,9 @@
|
||||
"endpoint_id": "DELETE /api/dms/{dms_instance_code}/v1/cd_geo_unit",
|
||||
"endpoint_name": "地质单元数据删除",
|
||||
"overall_status": "失败",
|
||||
"duration_seconds": 0.22315,
|
||||
"start_time": "2025-05-28T13:21:27.822536",
|
||||
"end_time": "2025-05-28T13:21:28.045686",
|
||||
"duration_seconds": 0.111646,
|
||||
"start_time": "2025-05-28T16:49:42.955037",
|
||||
"end_time": "2025-05-28T16:49:43.066683",
|
||||
"executed_test_cases": [
|
||||
{
|
||||
"test_case_id": "TC-STATUS-001",
|
||||
@ -508,8 +487,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "通过",
|
||||
"message": "响应状态码为 200,符合预期 200。",
|
||||
"duration_seconds": 0.034799834014847875,
|
||||
"timestamp": "2025-05-28T13:21:27.857482",
|
||||
"duration_seconds": 0.016340041998773813,
|
||||
"timestamp": "2025-05-28T16:49:42.971456",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -523,8 +502,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "通过",
|
||||
"message": "针对 DELETE http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit (状态码 200) 的响应体 conforms to the JSON schema.",
|
||||
"duration_seconds": 0.031897374894469976,
|
||||
"timestamp": "2025-05-28T13:21:27.889487",
|
||||
"duration_seconds": 0.015255250036716461,
|
||||
"timestamp": "2025-05-28T16:49:42.986761",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -538,8 +517,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "失败",
|
||||
"message": "API通过HTTP (http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit) 响应了成功的状态码 200,这违反了HTTPS强制策略。",
|
||||
"duration_seconds": 0.030905708903446794,
|
||||
"timestamp": "2025-05-28T13:21:27.920483",
|
||||
"duration_seconds": 0.019025625195354223,
|
||||
"timestamp": "2025-05-28T16:49:43.005840",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200
|
||||
@ -551,16 +530,16 @@
|
||||
"test_case_name": "Error Code 4001 - Query Parameter Type Mismatch Validation",
|
||||
"test_case_severity": "中",
|
||||
"status": "失败",
|
||||
"message": "当查询参数 'id' (路径: 'id') 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '2'.",
|
||||
"duration_seconds": 0.03202395816333592,
|
||||
"timestamp": "2025-05-28T13:21:27.952594",
|
||||
"message": "当查询参数 'id' (路径: 'id') 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '44'.",
|
||||
"duration_seconds": 0.015513875056058168,
|
||||
"timestamp": "2025-05-28T16:49:43.021404",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200,
|
||||
"response_body": {
|
||||
"code": 2,
|
||||
"message": "enim Lorem exercitation aute",
|
||||
"data": false
|
||||
"code": 44,
|
||||
"message": "culpa",
|
||||
"data": true
|
||||
},
|
||||
"expected_http_status_codes": [
|
||||
400,
|
||||
@ -576,15 +555,15 @@
|
||||
"test_case_name": "Error Code 4001 - Request Body Type Mismatch Validation",
|
||||
"test_case_severity": "中",
|
||||
"status": "失败",
|
||||
"message": "当请求体字段 'version' 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '37'.",
|
||||
"duration_seconds": 0.029567375080659986,
|
||||
"timestamp": "2025-05-28T13:21:27.982294",
|
||||
"message": "当请求体字段 'version' 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '62'.",
|
||||
"duration_seconds": 0.014269457897171378,
|
||||
"timestamp": "2025-05-28T16:49:43.035721",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200,
|
||||
"response_body": {
|
||||
"code": 37,
|
||||
"message": "deserunt",
|
||||
"code": 62,
|
||||
"message": "nisi id Ut est",
|
||||
"data": false
|
||||
},
|
||||
"expected_http_status_codes": [
|
||||
@ -602,8 +581,8 @@
|
||||
"test_case_severity": "高",
|
||||
"status": "通过",
|
||||
"message": "跳过测试:在API规范中未找到合适的必填请求体字段用于移除测试。",
|
||||
"duration_seconds": 0.029653416946530342,
|
||||
"timestamp": "2025-05-28T13:21:28.012192",
|
||||
"duration_seconds": 0.014604958007112145,
|
||||
"timestamp": "2025-05-28T16:49:43.050373",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -616,16 +595,16 @@
|
||||
"test_case_name": "Error Code 4003 - Missing Required Query Parameter Validation",
|
||||
"test_case_severity": "高",
|
||||
"status": "失败",
|
||||
"message": "当移除必填查询参数 'id' 时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4003'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '79'.",
|
||||
"duration_seconds": 0.0332651250064373,
|
||||
"timestamp": "2025-05-28T13:21:28.045619",
|
||||
"message": "当移除必填查询参数 'id' 时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4003'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '75'.",
|
||||
"duration_seconds": 0.016156750032678246,
|
||||
"timestamp": "2025-05-28T16:49:43.066589",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200,
|
||||
"response_body": {
|
||||
"code": 79,
|
||||
"message": "tempor proident",
|
||||
"data": false
|
||||
"code": 75,
|
||||
"message": "sint tempor laboris proident irure",
|
||||
"data": true
|
||||
},
|
||||
"expected_http_status_codes": [
|
||||
400,
|
||||
@ -642,9 +621,9 @@
|
||||
"endpoint_id": "POST /api/dms/{dms_instance_code}/v1/cd_geo_unit",
|
||||
"endpoint_name": "地质单元数据添加",
|
||||
"overall_status": "失败",
|
||||
"duration_seconds": 0.182421,
|
||||
"start_time": "2025-05-28T13:21:28.045730",
|
||||
"end_time": "2025-05-28T13:21:28.228151",
|
||||
"duration_seconds": 0.258948,
|
||||
"start_time": "2025-05-28T16:49:43.066754",
|
||||
"end_time": "2025-05-28T16:49:43.325702",
|
||||
"executed_test_cases": [
|
||||
{
|
||||
"test_case_id": "TC-STATUS-001",
|
||||
@ -652,8 +631,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "通过",
|
||||
"message": "响应状态码为 200,符合预期 200。",
|
||||
"duration_seconds": 0.023669250076636672,
|
||||
"timestamp": "2025-05-28T13:21:28.069531",
|
||||
"duration_seconds": 0.017587749985978007,
|
||||
"timestamp": "2025-05-28T16:49:43.084674",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -667,8 +646,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "通过",
|
||||
"message": "Schema验证步骤完成(未发现问题,或schema不适用/未为此响应定义)。",
|
||||
"duration_seconds": 0.022945916978642344,
|
||||
"timestamp": "2025-05-28T13:21:28.092561",
|
||||
"duration_seconds": 0.019916499964892864,
|
||||
"timestamp": "2025-05-28T16:49:43.104648",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -682,8 +661,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "失败",
|
||||
"message": "API通过HTTP (http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit) 响应了成功的状态码 200,这违反了HTTPS强制策略。",
|
||||
"duration_seconds": 0.025090625043958426,
|
||||
"timestamp": "2025-05-28T13:21:28.117746",
|
||||
"duration_seconds": 0.014826040947809815,
|
||||
"timestamp": "2025-05-28T16:49:43.119607",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200
|
||||
@ -696,8 +675,8 @@
|
||||
"test_case_severity": "中",
|
||||
"status": "通过",
|
||||
"message": "跳过测试:在查询参数中未找到合适的字段来测试类型不匹配。",
|
||||
"duration_seconds": 0.024663874879479408,
|
||||
"timestamp": "2025-05-28T13:21:28.142566",
|
||||
"duration_seconds": 0.01400312501937151,
|
||||
"timestamp": "2025-05-28T16:49:43.133661",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -710,16 +689,16 @@
|
||||
"test_case_name": "Error Code 4001 - Request Body Type Mismatch Validation",
|
||||
"test_case_severity": "中",
|
||||
"status": "失败",
|
||||
"message": "当请求体字段 'version' 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '15'.",
|
||||
"duration_seconds": 0.0300740001257509,
|
||||
"timestamp": "2025-05-28T13:21:28.172718",
|
||||
"message": "当请求体字段 'version' 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '99'.",
|
||||
"duration_seconds": 0.015632750000804663,
|
||||
"timestamp": "2025-05-28T16:49:43.149338",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200,
|
||||
"response_body": {
|
||||
"code": 15,
|
||||
"message": "cupidatat dolore incididunt anim",
|
||||
"data": false
|
||||
"code": 99,
|
||||
"message": "cupidatat",
|
||||
"data": true
|
||||
},
|
||||
"expected_http_status_codes": [
|
||||
400,
|
||||
@ -735,15 +714,15 @@
|
||||
"test_case_name": "Error Code 4003 - Missing Required Request Body Field Validation",
|
||||
"test_case_severity": "高",
|
||||
"status": "失败",
|
||||
"message": "当移除必填请求体字段 'data.0.bsflag' 时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4003'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '39'.",
|
||||
"duration_seconds": 0.02992445812560618,
|
||||
"timestamp": "2025-05-28T13:21:28.202756",
|
||||
"message": "当移除必填请求体字段 'data.0.bsflag' 时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4003'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '48'.",
|
||||
"duration_seconds": 0.07669858285225928,
|
||||
"timestamp": "2025-05-28T16:49:43.226093",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200,
|
||||
"response_body": {
|
||||
"code": 39,
|
||||
"message": "do ea ut in nulla",
|
||||
"code": 48,
|
||||
"message": "qui",
|
||||
"data": false
|
||||
},
|
||||
"expected_http_status_codes": [
|
||||
@ -761,8 +740,8 @@
|
||||
"test_case_severity": "高",
|
||||
"status": "通过",
|
||||
"message": "跳过测试:在API规范中未找到合适的必填查询参数用于移除测试。",
|
||||
"duration_seconds": 0.025168417021632195,
|
||||
"timestamp": "2025-05-28T13:21:28.228043",
|
||||
"duration_seconds": 0.09806604101322591,
|
||||
"timestamp": "2025-05-28T16:49:43.325657",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -776,9 +755,9 @@
|
||||
"endpoint_id": "GET /api/dms/{dms_instance_code}/v1/cd_geo_unit/{version}/{id}",
|
||||
"endpoint_name": "地质单元查询详情",
|
||||
"overall_status": "失败",
|
||||
"duration_seconds": 0.202336,
|
||||
"start_time": "2025-05-28T13:21:28.228223",
|
||||
"end_time": "2025-05-28T13:21:28.430559",
|
||||
"duration_seconds": 0.128568,
|
||||
"start_time": "2025-05-28T16:49:43.325730",
|
||||
"end_time": "2025-05-28T16:49:43.454298",
|
||||
"executed_test_cases": [
|
||||
{
|
||||
"test_case_id": "TC-STATUS-001",
|
||||
@ -786,8 +765,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "通过",
|
||||
"message": "响应状态码为 200,符合预期 200。",
|
||||
"duration_seconds": 0.03048399998806417,
|
||||
"timestamp": "2025-05-28T13:21:28.258932",
|
||||
"duration_seconds": 0.020079500041902065,
|
||||
"timestamp": "2025-05-28T16:49:43.345893",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -801,8 +780,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "通过",
|
||||
"message": "针对 GET http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0/example_id (状态码 200) 的响应体 conforms to the JSON schema.",
|
||||
"duration_seconds": 0.028292667120695114,
|
||||
"timestamp": "2025-05-28T13:21:28.287351",
|
||||
"duration_seconds": 0.015128917060792446,
|
||||
"timestamp": "2025-05-28T16:49:43.361072",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -816,8 +795,8 @@
|
||||
"test_case_severity": "严重",
|
||||
"status": "失败",
|
||||
"message": "API通过HTTP (http://127.0.0.1:4523/m1/6389742-6086420-default/api/dms/example_dms_instance_code/v1/cd_geo_unit/1.0.0/example_id) 响应了成功的状态码 200,这违反了HTTPS强制策略。",
|
||||
"duration_seconds": 0.027567417128011584,
|
||||
"timestamp": "2025-05-28T13:21:28.315044",
|
||||
"duration_seconds": 0.015682624885812402,
|
||||
"timestamp": "2025-05-28T16:49:43.376817",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200
|
||||
@ -830,8 +809,8 @@
|
||||
"test_case_severity": "中",
|
||||
"status": "通过",
|
||||
"message": "跳过测试:在查询参数中未找到合适的字段来测试类型不匹配。",
|
||||
"duration_seconds": 0.031142584048211575,
|
||||
"timestamp": "2025-05-28T13:21:28.346312",
|
||||
"duration_seconds": 0.015055665979161859,
|
||||
"timestamp": "2025-05-28T16:49:43.391929",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -844,21 +823,21 @@
|
||||
"test_case_name": "Error Code 4001 - Request Body Type Mismatch Validation",
|
||||
"test_case_severity": "中",
|
||||
"status": "失败",
|
||||
"message": "当请求体字段 'isSearchCount' 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '96'.",
|
||||
"duration_seconds": 0.025648624869063497,
|
||||
"timestamp": "2025-05-28T13:21:28.372139",
|
||||
"message": "当请求体字段 'isSearchCount' 类型不匹配时,期望API返回状态码在 [400, 422] 中,或返回4xx客户端错误且业务码为 '4001'. 实际收到状态码 200. 响应体中的业务码 ('code') 为 '74'.",
|
||||
"duration_seconds": 0.01596312504261732,
|
||||
"timestamp": "2025-05-28T16:49:43.407948",
|
||||
"validation_points": [
|
||||
{
|
||||
"status_code": 200,
|
||||
"response_body": {
|
||||
"code": 96,
|
||||
"message": "ea do ipsum consectetur",
|
||||
"code": 74,
|
||||
"message": "ipsum commodo dolore",
|
||||
"data": {
|
||||
"total": 73,
|
||||
"total": 100,
|
||||
"list": [
|
||||
{
|
||||
"dsid": "77",
|
||||
"dataRegion": "tempor dolore Ut",
|
||||
"dsid": "47",
|
||||
"dataRegion": "dolore aute Lorem",
|
||||
"gasReleaseMon": null,
|
||||
"gasReleaseYear": null,
|
||||
"releaseGasCum": null
|
||||
@ -881,8 +860,8 @@
|
||||
"test_case_severity": "高",
|
||||
"status": "通过",
|
||||
"message": "跳过测试:在API规范中未找到合适的必填请求体字段用于移除测试。",
|
||||
"duration_seconds": 0.030808875104412436,
|
||||
"timestamp": "2025-05-28T13:21:28.403049",
|
||||
"duration_seconds": 0.017927916953340173,
|
||||
"timestamp": "2025-05-28T16:49:43.425926",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
@ -896,8 +875,8 @@
|
||||
"test_case_severity": "高",
|
||||
"status": "通过",
|
||||
"message": "跳过测试:在API规范中未找到合适的必填查询参数用于移除测试。",
|
||||
"duration_seconds": 0.027266334043815732,
|
||||
"timestamp": "2025-05-28T13:21:28.430452",
|
||||
"duration_seconds": 0.028265791945159435,
|
||||
"timestamp": "2025-05-28T16:49:43.454254",
|
||||
"validation_points": [
|
||||
{
|
||||
"passed": true,
|
||||
|
||||
Binary file not shown.
356
run_api_tests.py
356
run_api_tests.py
@ -14,7 +14,9 @@ import json
|
||||
import logging
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
from ddms_compliance_suite.api_caller.caller import APICallDetail
|
||||
from ddms_compliance_suite.test_orchestrator import APITestOrchestrator, TestSummary
|
||||
|
||||
# 配置日志
|
||||
@ -32,8 +34,9 @@ def parse_args():
|
||||
# 基本参数
|
||||
parser.add_argument('--base-url', required=True, help='API基础URL')
|
||||
parser.add_argument('--verbose', '-v', action='store_true', help='启用详细日志')
|
||||
parser.add_argument('--output', '-o', help='输出文件路径')
|
||||
parser.add_argument('--format', choices=['json', 'html'], default='json', help='输出格式')
|
||||
parser.add_argument('--output', '-o', help='输出目录或主报告文件路径 (例如 ./test_reports/ 或 ./test_reports/summary.json)')
|
||||
parser.add_argument('--format', choices=['json', 'html'], default='json', help='主测试摘要报告的输出格式')
|
||||
parser.add_argument('--api-calls-output', help='API 调用详情的 Markdown 输出文件路径 (例如 ./test_reports/api_calls.md)。如果未提供,将尝试使用 --output 目录和默认文件名 api_call_details.md。始终会额外生成一个同名的 .txt 文件包含纯 cURL 命令。')
|
||||
|
||||
# API定义参数
|
||||
api_group = parser.add_argument_group('API定义源')
|
||||
@ -115,14 +118,22 @@ def list_swagger_tags(swagger_file: str):
|
||||
for i, tag in enumerate(parsed_swagger.tags, 1):
|
||||
print(f"{i}. {tag.get('name', '未命名')} - {tag.get('description', '无描述')}")
|
||||
|
||||
def save_results(summary: TestSummary, output_file: str, format_type: str):
|
||||
"""保存测试结果"""
|
||||
def save_results(summary: TestSummary, output_file_path: str, format_type: str):
|
||||
"""保存主测试摘要结果"""
|
||||
output_path = Path(output_file_path)
|
||||
# Ensure the directory for the output file exists
|
||||
try:
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
except OSError as e:
|
||||
logger.error(f"Error creating directory for output file {output_path.parent}: {e}")
|
||||
return
|
||||
|
||||
if format_type == 'json':
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(summary.to_json(pretty=True))
|
||||
logger.info(f"测试结果已保存为JSON: {output_file}")
|
||||
logger.info(f"测试结果已保存为JSON: {output_path}")
|
||||
elif format_type == 'html':
|
||||
# 创建简单的HTML报告
|
||||
# Creating simple HTML report
|
||||
html_content = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
@ -146,12 +157,12 @@ def save_results(summary: TestSummary, output_file: str, format_type: str):
|
||||
<h1>API测试报告</h1>
|
||||
<div class="summary">
|
||||
<h2>测试结果摘要</h2>
|
||||
<p>总测试数: {summary.total}</p>
|
||||
<p class="pass">通过: {summary.passed}</p>
|
||||
<p class="fail">失败: {summary.failed}</p>
|
||||
<p class="error">错误: {summary.error}</p>
|
||||
<p class="skip">跳过: {summary.skipped}</p>
|
||||
<p>成功率: {summary.success_rate:.2f}%</p>
|
||||
<p>总测试数: {summary.total_test_cases_executed}</p>
|
||||
<p class="pass">通过: {summary.test_cases_passed}</p>
|
||||
<p class="fail">失败: {summary.test_cases_failed}</p>
|
||||
<p class="error">错误: {summary.test_cases_error}</p>
|
||||
<p class="skip">跳过: {summary.test_cases_skipped_in_endpoint}</p>
|
||||
<p>成功率: {summary.test_case_success_rate:.2f}%</p>
|
||||
<p>总耗时: {summary.duration:.2f}秒</p>
|
||||
<p>开始时间: {summary.start_time.isoformat()}</p>
|
||||
<p>结束时间: {summary.end_time.isoformat() if summary.end_time else 'N/A'}</p>
|
||||
@ -161,24 +172,28 @@ def save_results(summary: TestSummary, output_file: str, format_type: str):
|
||||
<table>
|
||||
<tr>
|
||||
<th>端点</th>
|
||||
<th>测试用例ID</th>
|
||||
<th>测试用例名称</th>
|
||||
<th>状态</th>
|
||||
<th>消息</th>
|
||||
<th>响应码</th>
|
||||
<th>耗时(秒)</th>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
for result in summary.results:
|
||||
status_class = "pass" if result.status == "通过" else "fail" if result.status == "失败" else "error" if result.status == "错误" else "skip"
|
||||
response_code = result.api_response.status_code if result.api_response else "N/A"
|
||||
|
||||
html_content += f"""
|
||||
for endpoint_result in summary.detailed_results:
|
||||
for tc_result in endpoint_result.executed_test_cases:
|
||||
status_class = "pass" if tc_result.status == ExecutedTestCaseResult.Status.PASSED else \
|
||||
"fail" if tc_result.status == ExecutedTestCaseResult.Status.FAILED else \
|
||||
"error" if tc_result.status == ExecutedTestCaseResult.Status.ERROR else "skip"
|
||||
|
||||
html_content += f"""
|
||||
<tr>
|
||||
<td>{result.endpoint_id}</td>
|
||||
<td class="{status_class}">{result.status}</td>
|
||||
<td>{result.message}</td>
|
||||
<td>{response_code}</td>
|
||||
<td>{result.elapsed_time:.4f}</td>
|
||||
<td>{endpoint_result.endpoint_name} ({endpoint_result.endpoint_id})</td>
|
||||
<td>{tc_result.test_case_id}</td>
|
||||
<td>{tc_result.test_case_name}</td>
|
||||
<td class="{status_class}">{tc_result.status.value}</td>
|
||||
<td>{tc_result.message}</td>
|
||||
<td>{tc_result.duration:.4f}</td>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
@ -188,9 +203,147 @@ def save_results(summary: TestSummary, output_file: str, format_type: str):
|
||||
</html>
|
||||
"""
|
||||
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(html_content)
|
||||
logger.info(f"测试结果已保存为HTML: {output_file}")
|
||||
logger.info(f"测试结果已保存为HTML: {output_path}")
|
||||
|
||||
def save_api_call_details_to_file(api_call_details: List[APICallDetail], output_dir_path: str, filename: str = "api_call_details.md"):
|
||||
"""
|
||||
将API调用详情列表保存到指定目录下的 Markdown 文件中。
|
||||
同时,额外生成一个纯文本文件 (.txt),每行包含一个 cURL 命令。
|
||||
"""
|
||||
if not api_call_details:
|
||||
logger.info("没有API调用详情可供保存。")
|
||||
return
|
||||
|
||||
output_dir = Path(output_dir_path)
|
||||
try:
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
except OSError as e:
|
||||
logger.error(f"创建API调用详情输出目录 {output_dir} 失败: {e}")
|
||||
return
|
||||
|
||||
# 主文件是 Markdown 文件
|
||||
md_output_file = output_dir / filename
|
||||
# 确保它是 .md,尽管 main 函数应该已经处理了
|
||||
if md_output_file.suffix.lower() not in ['.md', '.markdown']:
|
||||
md_output_file = md_output_file.with_suffix('.md')
|
||||
|
||||
markdown_content = []
|
||||
|
||||
for detail in api_call_details:
|
||||
|
||||
# Request URL with params (if any)
|
||||
url_to_display = detail.request_url
|
||||
if detail.request_params:
|
||||
try:
|
||||
# Ensure urllib is available for this formatting step
|
||||
import urllib.parse
|
||||
query_string = urllib.parse.urlencode(detail.request_params)
|
||||
url_to_display = f"{detail.request_url}?{query_string}"
|
||||
except Exception as e:
|
||||
logger.warning(f"Error formatting URL with params for display: {e}")
|
||||
# Fallback to just the base URL if params formatting fails
|
||||
|
||||
markdown_content.append(f"## `{detail.request_method} {url_to_display}`")
|
||||
markdown_content.append("**cURL Command:**")
|
||||
markdown_content.append("```sh")
|
||||
markdown_content.append(detail.curl_command)
|
||||
markdown_content.append("```")
|
||||
markdown_content.append("### Request Details")
|
||||
markdown_content.append(f"- **Method:** `{detail.request_method}`")
|
||||
markdown_content.append(f"- **Full URL:** `{url_to_display}`")
|
||||
|
||||
markdown_content.append("- **Headers:**")
|
||||
markdown_content.append("```json")
|
||||
markdown_content.append(json.dumps(detail.request_headers, indent=2, ensure_ascii=False))
|
||||
markdown_content.append(" ```")
|
||||
|
||||
if detail.request_params:
|
||||
markdown_content.append("- **Query Parameters:**")
|
||||
markdown_content.append("```json")
|
||||
markdown_content.append(json.dumps(detail.request_params, indent=2, ensure_ascii=False))
|
||||
markdown_content.append(" ```")
|
||||
|
||||
if detail.request_body is not None:
|
||||
markdown_content.append("- **Body:**")
|
||||
body_lang = "text"
|
||||
formatted_body = str(detail.request_body)
|
||||
try:
|
||||
# Try to parse as JSON for pretty printing
|
||||
if isinstance(detail.request_body, str):
|
||||
try:
|
||||
parsed_json = json.loads(detail.request_body)
|
||||
formatted_body = json.dumps(parsed_json, indent=2, ensure_ascii=False)
|
||||
body_lang = "json"
|
||||
except json.JSONDecodeError:
|
||||
pass # Keep as text
|
||||
elif isinstance(detail.request_body, (dict, list)):
|
||||
formatted_body = json.dumps(detail.request_body, indent=2, ensure_ascii=False)
|
||||
body_lang = "json"
|
||||
except Exception as e:
|
||||
logger.warning(f"Error formatting request body for Markdown: {e}")
|
||||
|
||||
markdown_content.append(f"```{body_lang}")
|
||||
markdown_content.append(formatted_body)
|
||||
markdown_content.append(" ```")
|
||||
|
||||
markdown_content.append("### Response Details")
|
||||
markdown_content.append(f"- **Status Code:** `{detail.response_status_code}`")
|
||||
markdown_content.append(f"- **Elapsed Time:** `{detail.response_elapsed_time:.4f}s`")
|
||||
|
||||
markdown_content.append("- **Headers:**")
|
||||
markdown_content.append("```json")
|
||||
markdown_content.append(json.dumps(detail.response_headers, indent=2, ensure_ascii=False))
|
||||
markdown_content.append(" ```")
|
||||
|
||||
if detail.response_body is not None:
|
||||
markdown_content.append("- **Body:**")
|
||||
resp_body_lang = "text"
|
||||
formatted_resp_body = str(detail.response_body)
|
||||
try:
|
||||
# Try to parse as JSON for pretty printing
|
||||
if isinstance(detail.response_body, str):
|
||||
try:
|
||||
# If it's already a string that might be JSON, try parsing and re-dumping
|
||||
parsed_json_resp = json.loads(detail.response_body)
|
||||
formatted_resp_body = json.dumps(parsed_json_resp, indent=2, ensure_ascii=False)
|
||||
resp_body_lang = "json"
|
||||
except json.JSONDecodeError:
|
||||
# It's a string, but not valid JSON, keep as text
|
||||
pass
|
||||
elif isinstance(detail.response_body, (dict, list)):
|
||||
# It's already a dict/list, dump it as JSON
|
||||
formatted_resp_body = json.dumps(detail.response_body, indent=2, ensure_ascii=False)
|
||||
resp_body_lang = "json"
|
||||
# If it's neither string nor dict/list (e.g. int, bool from parsed json), str() is fine.
|
||||
except Exception as e:
|
||||
logger.warning(f"Error formatting response body for Markdown: {e}")
|
||||
|
||||
markdown_content.append(f"```{resp_body_lang}")
|
||||
markdown_content.append(formatted_resp_body)
|
||||
markdown_content.append(" ```")
|
||||
markdown_content.append("") # Add a blank line for spacing before next --- or EOF
|
||||
markdown_content.append("---") # Separator
|
||||
|
||||
try:
|
||||
with open(md_output_file, 'w', encoding='utf-8') as f_md:
|
||||
f_md.write("\n".join(markdown_content))
|
||||
logger.info(f"API调用详情已保存为 Markdown: {md_output_file}")
|
||||
except Exception as e:
|
||||
logger.error(f"保存API调用详情到 Markdown 文件 {md_output_file} 失败: {e}", exc_info=True)
|
||||
|
||||
# 额外生成 .txt 文件,只包含 cURL 命令
|
||||
txt_output_filename = md_output_file.with_suffix('.txt').name
|
||||
txt_output_file_path = output_dir / txt_output_filename
|
||||
|
||||
try:
|
||||
with open(txt_output_file_path, 'w', encoding='utf-8') as f_txt:
|
||||
for detail in api_call_details:
|
||||
f_txt.write(detail.curl_command + '\n')
|
||||
logger.info(f"可直接执行的cURL命令已保存到纯文本文件: {txt_output_file_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"保存cURL命令到文本文件 {txt_output_file_path} 失败: {e}", exc_info=True)
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
@ -201,27 +354,44 @@ def main():
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.debug("已启用详细日志模式")
|
||||
|
||||
# 检查是否提供了API定义源
|
||||
if not args.yapi and not args.swagger:
|
||||
logger.error("请提供API定义源:--yapi 或 --swagger")
|
||||
return 1
|
||||
sys.exit(1)
|
||||
|
||||
# 列出分类/标签
|
||||
if args.list_categories and args.yapi:
|
||||
list_yapi_categories(args.yapi)
|
||||
return 0
|
||||
sys.exit(0)
|
||||
|
||||
if args.list_tags and args.swagger:
|
||||
list_swagger_tags(args.swagger)
|
||||
return 0
|
||||
sys.exit(0)
|
||||
|
||||
# 解析分类/标签过滤器
|
||||
categories = args.categories.split(',') if args.categories else None
|
||||
tags = args.tags.split(',') if args.tags else None
|
||||
logger.info(f"args.api_key: {args.llm_api_key}")
|
||||
|
||||
# 实例化测试编排器
|
||||
# 将 custom_test_cases_dir 参数传递给 APITestOrchestrator 的构造函数
|
||||
DEFAULT_OUTPUT_DIR = Path("./test_reports")
|
||||
output_directory: Path
|
||||
main_report_file_path: Path
|
||||
|
||||
if args.output:
|
||||
output_arg_path = Path(args.output)
|
||||
if output_arg_path.suffix and output_arg_path.name: # Check if it looks like a file
|
||||
output_directory = output_arg_path.parent
|
||||
main_report_file_path = output_arg_path
|
||||
else:
|
||||
output_directory = output_arg_path
|
||||
main_report_file_path = output_directory / f"summary.{args.format}"
|
||||
else:
|
||||
output_directory = DEFAULT_OUTPUT_DIR
|
||||
main_report_file_path = output_directory / f"summary.{args.format}"
|
||||
|
||||
try:
|
||||
output_directory.mkdir(parents=True, exist_ok=True)
|
||||
logger.info(f"主输出目录设置为: {output_directory.resolve()}")
|
||||
except OSError as e:
|
||||
logger.error(f"创建主输出目录失败 {output_directory}: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
orchestrator = APITestOrchestrator(
|
||||
base_url=args.base_url,
|
||||
custom_test_cases_dir=args.custom_test_cases_dir,
|
||||
@ -231,56 +401,84 @@ def main():
|
||||
use_llm_for_request_body=args.use_llm_for_request_body,
|
||||
use_llm_for_path_params=args.use_llm_for_path_params,
|
||||
use_llm_for_query_params=args.use_llm_for_query_params,
|
||||
use_llm_for_headers=args.use_llm_for_headers
|
||||
use_llm_for_headers=args.use_llm_for_headers,
|
||||
output_dir=str(output_directory)
|
||||
)
|
||||
|
||||
# 运行测试
|
||||
summary = None
|
||||
test_summary: Optional[TestSummary] = None
|
||||
|
||||
if args.yapi:
|
||||
logger.info(f"从YAPI文件运行测试: {args.yapi}")
|
||||
summary = orchestrator.run_tests_from_yapi(
|
||||
yapi_file_path=args.yapi,
|
||||
categories=categories,
|
||||
custom_test_cases_dir=args.custom_test_cases_dir # 也传递给具体执行方法,以支持运行时覆盖
|
||||
)
|
||||
elif args.swagger:
|
||||
logger.info(f"从Swagger文件运行测试: {args.swagger}")
|
||||
summary = orchestrator.run_tests_from_swagger(
|
||||
swagger_file_path=args.swagger,
|
||||
tags=tags,
|
||||
custom_test_cases_dir=args.custom_test_cases_dir # 也传递给具体执行方法
|
||||
)
|
||||
else:
|
||||
logger.error("必须提供YAPI或Swagger文件路径")
|
||||
|
||||
if not summary:
|
||||
logger.error("测试执行失败")
|
||||
return 1
|
||||
# 打印结果摘要
|
||||
summary.print_summary_to_console()
|
||||
|
||||
# 保存结果
|
||||
if args.output:
|
||||
save_results(summary, args.output, args.format)
|
||||
|
||||
# 根据测试结果设置退出码
|
||||
# 直接访问 TestSummary 的属性
|
||||
has_endpoint_errors = summary.endpoints_error > 0
|
||||
has_endpoint_failures = summary.endpoints_failed > 0
|
||||
|
||||
# 或者,也可以关注测试用例级别的失败/错误
|
||||
# has_test_case_errors = summary.test_cases_error > 0
|
||||
# has_test_case_failures = summary.test_cases_failed > 0
|
||||
try:
|
||||
if args.yapi:
|
||||
logger.info(f"从YAPI文件运行测试: {args.yapi}")
|
||||
test_summary = orchestrator.run_tests_from_yapi(
|
||||
yapi_file_path=args.yapi,
|
||||
categories=categories,
|
||||
custom_test_cases_dir=args.custom_test_cases_dir
|
||||
)
|
||||
elif args.swagger:
|
||||
logger.info(f"从Swagger文件运行测试: {args.swagger}")
|
||||
test_summary = orchestrator.run_tests_from_swagger(
|
||||
swagger_file_path=args.swagger,
|
||||
tags=tags,
|
||||
custom_test_cases_dir=args.custom_test_cases_dir
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"执行测试时发生意外错误: {e}", exc_info=True)
|
||||
sys.exit(1)
|
||||
|
||||
if has_endpoint_errors or has_endpoint_failures:
|
||||
return 1 # 表示测试运行中存在失败或错误
|
||||
else:
|
||||
return 0 # 所有端点测试通过或部分成功(无错误或关键失败)
|
||||
if test_summary:
|
||||
save_results(test_summary, str(main_report_file_path), args.format)
|
||||
|
||||
api_calls_output_path_str: Optional[str] = None
|
||||
# 默认文件名现在是 .md
|
||||
api_calls_filename: str = "api_call_details.md"
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
if args.api_calls_output:
|
||||
api_calls_output_file = Path(args.api_calls_output)
|
||||
# 确保后缀是 .md,如果用户提供了其他后缀或没有后缀
|
||||
if api_calls_output_file.suffix.lower() not in ['.md', '.markdown']:
|
||||
api_calls_output_file = api_calls_output_file.with_suffix('.md')
|
||||
logger.info(f"API调用详情输出文件名已调整为 Markdown 格式: {api_calls_output_file.name}")
|
||||
|
||||
api_calls_output_path_str = str(api_calls_output_file.parent)
|
||||
api_calls_filename = api_calls_output_file.name
|
||||
logger.info(f"API调用详情将以 Markdown 格式保存到: {api_calls_output_file}")
|
||||
elif args.output:
|
||||
output_arg_path = Path(args.output)
|
||||
if output_arg_path.is_dir():
|
||||
api_calls_output_path_str = str(output_arg_path)
|
||||
else:
|
||||
api_calls_output_path_str = str(output_arg_path.parent)
|
||||
logger.info(f"API调用详情将以 Markdown 格式保存到目录 '{api_calls_output_path_str}' (使用默认文件名 '{api_calls_filename}')")
|
||||
else:
|
||||
api_calls_output_path_str = "."
|
||||
logger.info(f"API调用详情将以 Markdown 格式保存到当前目录 '.' (使用默认文件名 '{api_calls_filename}')")
|
||||
|
||||
# 保存API调用详情
|
||||
if orchestrator and api_calls_output_path_str:
|
||||
save_api_call_details_to_file(
|
||||
orchestrator.get_api_call_details(),
|
||||
api_calls_output_path_str,
|
||||
filename=api_calls_filename
|
||||
)
|
||||
|
||||
# Improved HTML report summary access
|
||||
failed_count = getattr(test_summary, 'endpoints_failed', 0) + getattr(test_summary, 'test_cases_failed', 0)
|
||||
error_count = getattr(test_summary, 'endpoints_error', 0) + getattr(test_summary, 'test_cases_error', 0)
|
||||
|
||||
if failed_count > 0 or error_count > 0:
|
||||
logger.info("部分测试失败或出错,请检查报告。")
|
||||
# sys.exit(1) # Keep this commented if a report is always desired regardless of outcome
|
||||
else:
|
||||
logger.info("所有测试完成。")
|
||||
else:
|
||||
logger.error("未能生成测试摘要。")
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
# python run_api_tests.py --base-url http://127.0.0.1:4523/m1/6389742-6086420-default --swagger assets/doc/井筒API示例swagger.json --custom-test-cases-dir ./custom_testcases \
|
||||
# --verbose \
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['run_api_tests.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='run_api_tests',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
853
test_report.json
853
test_report.json
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user