add:output api call details and curl

This commit is contained in:
gongwenxin 2025-05-28 17:58:20 +08:00
parent 936714242f
commit 0585dd0b29
14 changed files with 11179 additions and 3008 deletions

2114
apis.yaml Normal file

File diff suppressed because it is too large Load Diff

3344
apis0.md Normal file

File diff suppressed because it is too large Load Diff

42
apis0.txt Normal file
View 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

File diff suppressed because it is too large Load Diff

View File

@ -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))

View File

@ -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],

4734
log.txt

File diff suppressed because it is too large Load Diff

View File

@ -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.

View File

@ -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 \

View File

@ -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,
)

File diff suppressed because it is too large Load Diff