Merge branch 'feat/scripts' of https://github.com/eolinker/eoapi into feat/scripts

This commit is contained in:
buqiyuan 2022-07-14 19:01:00 +08:00
commit bdbad60eea
14 changed files with 223 additions and 74 deletions

1
.gitignore vendored
View File

@ -10,6 +10,7 @@ out/
*.js
*.js.map
*.webpack.js
!src/workbench/node/electron/**/*.js
!src/workbench/node/request/**/*.js
!src/workbench/node/server/**/*.js
!/api/*.js

View File

@ -34,7 +34,7 @@
<!-- Request Headers -->
<nz-tab [nzTitle]="headerTitleTmp" [nzForceRender]="true">
<ng-template #headerTitleTmp>
<span i18n>Request Headers</span>
<span i18n="@@RequestHeaders">Headers</span>
<span class="eo-tab-icon ml-[4px]" *ngIf="bindGetApiParamNum(apiData.requestHeaders)">{{
apiData.requestHeaders | apiParamsNum
}}</span>
@ -76,7 +76,7 @@
</nz-tab>
<nz-tab [nzTitle]="preScriptTitleTmp" [nzForceRender]="true">
<ng-template #preScriptTitleTmp>
Before script
Pre-request Script
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.restParams)">{{
apiData.restParams | apiParamsNum
}}</span>
@ -85,7 +85,7 @@
</nz-tab>
<nz-tab [nzTitle]="suffixScriptTitleTmp" [nzForceRender]="true">
<ng-template #suffixScriptTitleTmp>
After Script
After-response Script
<span class="eo-tab-icon" *ngIf="bindGetApiParamNum(apiData.restParams)">{{
apiData.restParams | apiParamsNum
}}</span>

View File

@ -27,6 +27,7 @@ import { StorageService } from '../../../shared/services/storage';
import { TestServerLocalNodeService } from '../../../shared/services/api-test/local-node/test-connect.service';
import { TestServerServerlessService } from '../../../shared/services/api-test/serverless-node/test-connect.service';
import { TestServerRemoteService } from 'eo/workbench/browser/src/app/shared/services/api-test/remote-node/test-connect.service';
import { ApiTestRes } from 'eo/workbench/browser/src/app/shared/services/api-test/test-server.model';
@Component({
selector: 'eo-api-test',
@ -185,13 +186,17 @@ export class ApiTestComponent implements OnInit, OnDestroy {
/**
* Receive Test Server Message
*/
private receiveMessage(message) {
private receiveMessage(message: ApiTestRes) {
console.log('receiveMessage', message);
const tmpHistory = {
general: message.general,
request: message.report.request,
request: message.report?.request,
response: message.response,
};
this.testResult = tmpHistory;
this.status$.next('tested');
if (message.status === 'error') return;
//If test sucess,addHistory
// other tab test finish,support multiple tab test same time
if (message.id && this.apiTab.tabID !== message.id) {
this.apiTab.tabCache[message.id].testResult = tmpHistory;
@ -201,9 +206,7 @@ export class ApiTestComponent implements OnInit, OnDestroy {
}
return;
}
this.testResult = tmpHistory;
this.addHistory(message, this.apiData.uuid);
this.status$.next('tested');
}
/**
* Change test status

View File

@ -23,6 +23,8 @@ import { NzEmptyModule } from 'ng-zorro-antd/empty';
import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';
import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
import { NzTypographyModule } from 'ng-zorro-antd/typography';
import { NzAlertModule } from 'ng-zorro-antd/alert';
import { ByteToStringPipe } from './result-response/get-size.pipe';
@ -57,6 +59,7 @@ const NZ_COMPONETS = [
NzEmptyModule,
NzPopconfirmModule,
NzToolTipModule,
NzAlertModule,
NzTypographyModule,
];
const COMPONENTS = [

View File

@ -1,44 +1,61 @@
<div class="pb15" *ngIf="!model.responseType">
<div class="pb15" *ngIf="(model | json) === '{}'">
<nz-empty nzNotFoundImage="simple" [nzNotFoundContent]="contentTpl">
<ng-template #contentTpl>
<span i18n>Click the Send button to get a test report</span>
</ng-template>
</nz-empty>
</div>
<div class="p15" *ngIf="model.responseType" [ngSwitch]="model.responseType">
<div class="mb15 basic_info_bar cw f_row f_js {{ model.statusCode ? codeStatus.class : 'code_red' }}">
<div class="p15" *ngIf="(model | json) !== '{}'">
<!-- Status Bar -->
<div
*ngIf="model.statusCode"
class="mb15 basic_info_bar cw f_row f_js {{ model.statusCode ? codeStatus.class : 'code_red' }}"
>
<div class="fs16" id="statusCode">{{ model.statusCode || 'No Response' }}</div>
<div class="f_row_ac fs12">
<span class="mr15" id="size">Size: {{ model.responseLength | byteToString }}</span>
<span id="time">Time: {{ model.testDeny }}ms</span>
</div>
</div>
<div class="text-center" *ngSwitchCase="'stream'">
<div *ngIf="!responseIsImg" i18n>
Unable to preview non-text type data, you can<button
class="eo_theme_btn_default mlr5"
type="button"
(click)="downloadResponseText()"
>
download back result</button
>and open it with other programs.
<!-- Test Alert Tip -->
<nz-alert
class="eo_alert_bar"
*ngFor="let item of model.reportList"
[nzType]="item.type === 'interrupt' ? 'error' : 'info'"
[nzMessage]="item.content || item"
nzShowIcon
></nz-alert>
<!-- Response -->
<div *ngIf="model.responseType" [ngSwitch]="model.responseType">
<div class="text-center" *ngSwitchCase="'stream'">
<div *ngIf="!responseIsImg" i18n>
Unable to preview non-text type data, you can<button
class="eo_theme_btn_default mlr5"
type="button"
(click)="downloadResponseText()"
>
download back result</button
>and open it with other programs.
</div>
<!-- <div class="mt20" *ngIf="responseIsImg">
<img class="maw_100percent" [src]="model.blobUrl" />
</div> -->
</div>
<!-- <div class="mt20" *ngIf="responseIsImg">
<img class="maw_100percent" [src]="model.blobUrl" />
</div> -->
<div class="text-center" *ngSwitchCase="'longText'" i18n>
Unable to preview non-text type data, you can
<button class="eo_theme_btn_default mlr5" type="button" (click)="downloadResponseText()">
download back result
</button>
<!-- or
<button class="eo_theme_btn_default" type="button" (click)="newTabResponseText()">在新标签页中显示返回结果</button>
and open it with other programs. -->
</div>
<eo-editor
*ngSwitchDefault
class="mt20"
[autoFormat]="true"
[(code)]="model.body"
[eventList]="['type', 'format', 'copy', 'download', 'newTab', 'search']"
></eo-editor>
</div>
<div class="text-center" *ngSwitchCase="'longText'" i18n>
Unable to preview non-text type data, you can
<button class="eo_theme_btn_default mlr5" type="button" (click)="downloadResponseText()">download back result</button>
<!-- or
<button class="eo_theme_btn_default" type="button" (click)="newTabResponseText()">在新标签页中显示返回结果</button>
and open it with other programs. -->
</div>
<eo-editor
*ngSwitchDefault
class="mt20"
[autoFormat]="true"
[(code)]="model.body"
[eventList]="['type', 'format', 'copy', 'download', 'newTab', 'search']"
></eo-editor>
</div>

View File

@ -22,3 +22,11 @@
background-color: var(--RED_NORMAL);
border-color: var(--RED_NORMAL);
}
::ng-deep {
.eo_alert_bar {
margin-bottom: 5px;
.ant-alert-info {
background-color: #fff;
}
}
}

View File

@ -1,16 +1,10 @@
<div class="flex eo-api-script">
<div class="w-[340px] h-[322px] overflow-auto">
<div class="flex justify-between p-3">
<div i18n>Shortcut</div>
<div i18n>Snippets</div>
<div>
<a
href="https://help.eolink.com/#/tutorial/?groupID=c-709&productID=13"
class="text-blue-400"
target="_blank"
rel="noopener noreferrer"
i18n
>Built-in function manual</a
>
<a href="https://eoapi.io/docs/script-function" class="text-blue-400" target="_blank"
rel="noopener noreferrer" i18n>Learn more</a>
</div>
</div>
<nz-tree-view [nzTreeControl]="treeControl" [nzDataSource]="dataSource" [nzBlockNode]="true">

View File

@ -2,6 +2,7 @@ import { listToTreeHasLevel } from '../../../utils/tree/tree.utils';
import { formatDate } from '@angular/common';
import { TestLocalNodeData } from './local-node/api-server-data.model';
import { ApiBodyType, ApiTestResGeneral, ApiTestHistoryResponse } from '../storage/index.model';
import { ApiTestRes } from 'eo/workbench/browser/src/app/shared/services/api-test/test-server.model';
const METHOD = ['POST', 'GET', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH'],
PROTOCOL = ['http', 'https'],
REQUEST_BODY_TYPE = ['formData', 'raw', 'json', 'xml', 'binary'];
@ -108,21 +109,33 @@ export const eoFormatRequestData = (data, opts = { env: {}, beforeScript: '', af
return result;
};
export const eoFormatResponseData = ({ report, history, id }) => {
let { httpCode, ...response } = history.resultInfo;
console.log(report, history, id);
let result: ApiTestRes;
if (['error'].includes(report.status)) {
result = {
status: 'error',
id,
response: {
reportList: [
{
type: 'interrupt',
content: report.errorReason,
},
...report.reportList,
],
},
};
return result;
}
let { httpCode, ...response } = history.resultInfo;
response = {
statusCode: httpCode,
...response,
body: response.body || '',
headers: response.headers.map((val) => ({ name: val.key, value: val.value })),
};
let result: {
report: any;
general: ApiTestResGeneral;
response: ApiTestHistoryResponse;
history: any;
id: number;
} = {
result = {
status: 'finish',
id: id,
general: report.general,
response: { blobFileName: report.blobFileName, ...response },

View File

@ -1,8 +1,16 @@
import { Environment, ApiTestHistoryFrame } from '../storage/index.model';
import { Environment, ApiTestHistoryResponse, ApiTestResGeneral, ApiTestHistoryFrame } from '../storage/index.model';
export interface ApiTestRes{
status:'finish'|'error',
id: number;
response: ApiTestHistoryResponse|any;
report?: any;
general?: ApiTestResGeneral;
history?: ApiTestHistoryFrame|any;
}
export interface TestServer {
init: (receiveMessage: (message: any) => void) => void;
send: (action: string, message: any) => void;
formatRequestData: (apiData, opts: { env: Environment; beforeScript: string; afterScript: string }) => any;
formatResponseData: (res) => { report: any; history: ApiTestHistoryFrame };
formatResponseData: (res) => ApiTestRes;
close: () => void;
}

View File

@ -116,7 +116,7 @@ export interface ApiTestHistoryResponse {
/**
* Inject Code println
*/
reportList: string[] | object[];
reportList: string[] | {type:'throw'|'interrupt',content:string}[];
}
/**

View File

@ -0,0 +1,23 @@
let _LibsFlowCommon = require('../request/unit.js');
let _LibsCommon = require('../request/libs/common.js');
process.on('message', async (message) => {
switch (message.action) {
case 'ajax': {
message.data.env = _LibsCommon.parseEnv(message.data.env);
await new _LibsFlowCommon.core().main(message.data).then(({ report, history }) => {
['general', 'requestInfo', 'resultInfo'].forEach((keyName) => {
if (typeof history[keyName] === 'string') history[keyName] = JSON.parse(history[keyName]);
});
process.send({
action: 'finish',
data:{
id: message.id,
report: report,
history: history,
}
});
});
break;
}
}
});

View File

@ -1,25 +1,31 @@
import * as child_process from 'child_process';
import { BrowserView } from 'electron';
import _LibsFlowCommon from '../request/unit';
import _LibsCommon from '../request/libs/common';
export class UnitWorker {
instance: child_process.ChildProcess;
view: BrowserView;
constructor(view: BrowserView) {
this.view = view;
}
async start(message: any) {
message.data.env = _LibsCommon.parseEnv(message.data.env);
await new _LibsFlowCommon.core().main(message.data).then(({ report, history }) => {
['general', 'requestInfo', 'resultInfo'].forEach((keyName) => {
if (typeof history[keyName] === 'string') history[keyName] = JSON.parse(history[keyName]);
});
this.finish({
id: message.id,
report: report,
history: history,
});
});
start(message: any) {
this.instance = child_process.fork(`${__dirname}/forkUnit.js`);
this.watch();
this.instance.send(message);
}
finish(message: any) {
this.view.webContents.send('unitTest', message);
this.kill();
}
kill() {
this.instance.kill();
}
private watch() {
this.instance.on('message', (message: any) => {
switch (message.action) {
case 'finish':{
this.finish(message.data);
break;
}
}
});
}
}

View File

@ -1,9 +1,83 @@
{
"en": {
"213d3b0f-b267-4d5c-9512-0a06e2a5a522": "JWT signature construction failed"
"jsonpath_syntax_error": "Json Path ? Syntax error",
"warning_when_is_invalid_by_express": "The verification rule of ? is ? ?, but it is ?",
"field_jsonpath_match_error":"According to the JSON path matching rule, the ? matching value is: ?, which does not conform to the verification rule: ??",
"xpath_match_error_tip_prefix": "xPath ? Syntax error,",
"json_structure_match_error_tip_prefix": "Field ? does not match,",
"json_path_match_error_tip_prefix": "Json Path ? Syntax error,",
"field_is_not_exist":"The parameter ? does not exist",
"field_type_is_invalid": "Field ? does not match. The verification type is ? , but it is ?",
"response_json_match_root_is_not_object": "The response root type non object",
"response_json_match_root_is_not_array": "The response root type non array",
"response_json_match_is_not_json": "The response JSON parsing erro",
"response_xml_match_is_not_xml": "The response XML parsing error",
"response_total_match_error": "Response match failed",
"response_regex_match_error": "The response regular matching failure",
"http_code_is_not_match":"Status code mismatchMatching rule is: ?, actual return: ?",
"response_header_is_not_exist":"Response header ? does not exist",
"response_header_is_not_match":"Response header ? result does not match, content verification is ? ?, but it is ?",
"unfoundErr":"unfound",
"sqlExecuteErrMsg":"Execute error, error message: ?",
"fnCodeExecuteErrMsg":"Execute error, please check the code ${tmpErrorLine?`(${tmpErrorLine}row, ${tmpErrorColumn} column)`:``}, error message: ${Err.message}",
"codeExecuteErrMsg":"Execute error, please check the code ${tmpErrorLine?`(${tmpErrorLine} row, ${tmpErrorColumn} column)`:``}, error message: ${Err.message}",
"publicFnCompileErrMsg": "The ? function is running incorrectly. Please check the ? function ?, error message: ?",
"publicFnExecuteErrMsg": "The ${tmpFnName} function is running incorrectly. Please check the ${tmpFnName} function ${tmpErrorLine?`(${tmpErrorLine} row,${tmpErrorColumn} column)`:``}, error message: ${Err.message}",
"requestPreReduceErrMsg": "${tmpTitle} run error, please check the code ${tmpErrorLine?`(${tmpErrorLine} row,${tmpErrorColumn} column)`:``}, error message: ${Err.message}",
"responsePreReduceErrMsg": "${tmpTitle||'Response Pre-process'} run error, please check the code ${tmpErrorLine?`(${tmpErrorLine} row,${tmpErrorColumn} column)`:``}, error message: ${Err.message}",
"63be68fa-31fc-498c-b49c-d3b6db10a95b": "Access to the local area is not supported for the moment. If you want to continue, please use the officially provided test enhancement plug-in!",
"d6fa1d73-6a43-477f-a6df-6752661c9df3": "Illegal request address!",
"a589da5d-3c96-487c-8aaa-645acb3bd8f6": "`${tmpTitle||'Response Pre-process'} trigger terminate request`",
"a89c785a-6075-4c04-8a60-28436f04e1f9": "[binary]${filename} parsing error",
"152df5bb-290c-4672-8173-0dae3fa4540f": "The database is unable to connect. Please check whether the database connection information is correct or whether the access white list is set for EOLINKER",
"e5fe1d17-67c4-473e-8910-bbdd79d0876a": "Error generating data by mock",
"def96c7b-94dd-4cbb-9f7f-917083c80750": "Construction failed, JavaScript syntax error",
"042723b6-253e-4920-8293-793ee76074d8": "Invalid link!",
"d9b74e6f-6071-4708-83b6-7db662d27502": "trigger function abort",
"650ebbf9-d07d-47df-b911-6d90d2ece0fc": "\\XML parsing error: \\n",
"213d3b0f-b267-4d5c-9512-0a06e2a5a522": "JWT signature construction failed",
"42c487b2-4b68-4dd1-834e-e1c978c8ea51": "`${tmpTitle||'Request Pre-process'} triggers termination request`",
"40867a93-0dad-49ce-971c-5da427f1d817": " parsing error",
"144010a7-f39a-4fc6-be20-31df96f36c0b":"`According to the JSON path matching rule, the ${inputOptions.paramKey} matching value is: ${JSON.stringify(inputResponseData).replace(/^\\[/,'').replace(/\\]$/,'')}, which does not conform to the verification rule: ${CONST_MATCH_STR[inputOptions.matchRule]}${tmpOriginalTextStr}"
},
"cn": {
"213d3b0f-b267-4d5c-9512-0a06e2a5a522": "jwt签名构造失败"
"jsonpath_syntax_error": "Json Path ? 语法错误",
"warning_when_is_invalid_by_express": "?内容校验为 ? ?,实际返回 ?",
"field_jsonpath_match_error":"根据JSON Path匹配规则? ,找到匹配值为:?,不符合校验规则:??",
"xpath_match_error_tip_prefix": "xPath ? 解析结果不匹配,",
"json_structure_match_error_tip_prefix": "字段 ? 结果不匹配,",
"json_path_match_error_tip_prefix": "Json Path ? 解析结果不匹配,",
"field_is_not_exist":"返回结果参数名 ? 不存在",
"field_type_is_invalid": "字段 ? 结果不匹配,校验类型为 ? ,实际返回类型为 ?",
"response_json_match_root_is_not_object": "返回结果根类型非Object",
"response_json_match_root_is_not_array": "返回结果根类型非Array",
"response_json_match_is_not_json": "返回结果JSON解析错误",
"response_xml_match_is_not_xml": "返回结果XML解析错误",
"response_total_match_error": "返回内容完全匹配失败",
"response_regex_match_error": "返回内容正则匹配失败",
"http_code_is_not_match":"状态码不匹配,匹配规则为: ? ,实际返回: ?",
"response_header_is_not_exist":"返回头部 ? 不存在",
"response_header_is_not_match":"返回头部 ? 结果不匹配,内容校验为 ? ?,实际返回 ?",
"assertError": "`触发${tmpTitle||'自定义校验规则'}中eo.error()函数`",
"unfoundErr":"不存在",
"sqlExecuteErrMsg":"执行异常,错误原因:?",
"fnCodeExecuteErrMsg":"运行错误,请检查代码${tmpErrorLine?`${tmpErrorLine}行,${tmpErrorColumn}列)`:``},错误信息:${Err.message}",
"codeExecuteErrMsg":"运行错误,请检查代码${tmpErrorLine?`${tmpErrorLine}行,${tmpErrorColumn}列)`:``},错误信息:${Err.message}",
"publicFnCompileErrMsg": "?函数运行错误,请检查?函数?,错误信息:?",
"publicFnExecuteErrMsg": "${tmpFnName}函数运行错误,请检查${tmpFnName}函数${tmpErrorLine?`${tmpErrorLine}行,${tmpErrorColumn}列)`:``},错误信息:${Err.message}",
"requestPreReduceErrMsg": "${tmpTitle}运行错误,请检查代码${tmpErrorLine?`${tmpErrorLine}行,${tmpErrorColumn}列)`:``},错误信息:${Err.message}",
"responsePreReduceErrMsg": "${tmpTitle||'后置脚本'}运行错误,请检查代码${tmpErrorLine?`${tmpErrorLine}行,${tmpErrorColumn}列)`:``},错误信息:${Err.message}",
"63be68fa-31fc-498c-b49c-d3b6db10a95b": "暂且不提供访问本地,若想继续,请使用官方所提供的测试增强插件!",
"d6fa1d73-6a43-477f-a6df-6752661c9df3": "请求地址非法!",
"a589da5d-3c96-487c-8aaa-645acb3bd8f6": "`触发${tmpTitle||'返回数据生成代码'}中eo.stop()函数,请求终止`",
"a89c785a-6075-4c04-8a60-28436f04e1f9": "[binary]${filename}解析错误",
"152df5bb-290c-4672-8173-0dae3fa4540f": "数据库无法连接请检查数据库连接信息是否正确或者是否为EOLINKER设置访问白名单",
"d9b74e6f-6071-4708-83b6-7db662d27502": "触发函数中止",
"213d3b0f-b267-4d5c-9512-0a06e2a5a522": "jwt签名构造失败",
"42c487b2-4b68-4dd1-834e-e1c978c8ea51": "`触发${tmpTitle||'自定义校验规则'}中eo.stop()函数,请求终止`",
"40867a93-0dad-49ce-971c-5da427f1d817": "解析错误",
"144010a7-f39a-4fc6-be20-31df96f36c0b":"`根据JSON Path匹配规则${inputOptions.paramKey} 找到匹配值为:${JSON.stringify(inputResponseData).replace(/^\\[/,'').replace(/\\]$/,'')},不符合校验规则:${CONST_MATCH_STR[inputOptions.matchRule]}${tmpOriginalTextStr}`",
"86acaf28-2e2e-d8c4-82be-3db350d83e9d":"触发eo.error()函数"
}
}

View File

@ -538,7 +538,6 @@ privateFun.parseBeforeCode = function (inputData, inputScript, inputOpts = {}) {
}
tmpVm.run(_LibsCommon.infiniteLoopDetector.wrap(inputScript || '', 'eo.infiniteLoopDetector'));
} catch (Err) {
console.log(Err);
switch (Err) {
case 'info':
case 'interrupt':
@ -583,7 +582,7 @@ privateFun.parseBeforeCode = function (inputData, inputScript, inputOpts = {}) {
return {
status: tmpStatus,
content: tmpErrorContent,
url: tmpTargetTypeData.apiUrl,
url: tmpTargetTypeData.url.parse(),
headers: tmpTargetTypeData.headerParam,
params: tmpTargetTypeData.bodyParseParam || tmpTargetTypeData.bodyParam,
env: privateFun.resetEnv(tmpBasicEnv, tmpCodeEvalObj.eo.env),