Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
xiaofang li
MeterSphere
Commits
c5d58d40
Commit
c5d58d40
authored
4 years ago
by
fit2-zhao
Browse files
Options
Download
Email Patches
Plain Diff
feat(接口定义): JSONpath 断言增加操作符
parent
b80da9b9
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
backend/pom.xml
+6
-0
backend/pom.xml
backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java
+1
-0
.../metersphere/api/dto/automation/parse/MsJmeterParser.java
backend/src/main/java/io/metersphere/api/dto/definition/request/assertions/MsAssertionJsonPath.java
+1
-0
...to/definition/request/assertions/MsAssertionJsonPath.java
backend/src/main/java/io/metersphere/api/dto/definition/request/assertions/MsAssertions.java
+6
-1
...e/api/dto/definition/request/assertions/MsAssertions.java
backend/src/main/java/io/metersphere/xmind/XmindCaseParser.java
+2
-2
...d/src/main/java/io/metersphere/xmind/XmindCaseParser.java
backend/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java
+252
-0
.../java/org/apache/jmeter/assertions/JSONPathAssertion.java
frontend/src/business/components/api/definition/components/assertion/ApiAssertionJsonPath.vue
+26
-2
.../definition/components/assertion/ApiAssertionJsonPath.vue
frontend/src/business/components/api/definition/components/assertion/ApiAssertions.vue
+29
-24
...nts/api/definition/components/assertion/ApiAssertions.vue
with
323 additions
and
29 deletions
+323
-29
backend/pom.xml
+
6
-
0
View file @
c5d58d40
...
...
@@ -422,6 +422,12 @@
<version>
${jmeter.version}
</version>
</dependency>
<!-- 添加jmeter包支持导入的jmx能正常执行 -->
<dependency>
<groupId>
com.jayway.jsonpath
</groupId>
<artifactId>
json-path
</artifactId>
<version>
2.5.0
</version>
</dependency>
</dependencies>
<build>
...
...
This diff is collapsed.
Click to expand it.
backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java
+
1
-
0
View file @
c5d58d40
...
...
@@ -489,6 +489,7 @@ public class MsJmeterParser extends ApiImportAbstractParser<ScenarioImport> {
assertionJsonPath
.
setDescription
(
jsonPathAssertion
.
getName
());
assertionJsonPath
.
setExpression
(
jsonPathAssertion
.
getJsonPath
());
assertionJsonPath
.
setExpect
(
jsonPathAssertion
.
getExpectedValue
());
assertionJsonPath
.
setOption
(
jsonPathAssertion
.
getPropertyAsString
(
"ASS_OPTION"
));
assertions
.
setName
(
jsonPathAssertion
.
getName
());
assertions
.
getJsonPath
().
add
(
assertionJsonPath
);
}
else
if
(
key
instanceof
XPath2Assertion
)
{
...
...
This diff is collapsed.
Click to expand it.
backend/src/main/java/io/metersphere/api/dto/definition/request/assertions/MsAssertionJsonPath.java
+
1
-
0
View file @
c5d58d40
...
...
@@ -10,6 +10,7 @@ public class MsAssertionJsonPath extends MsAssertionType {
private
String
expect
;
private
String
expression
;
private
String
description
;
private
String
option
=
"REGEX"
;
public
MsAssertionJsonPath
()
{
setType
(
MsAssertionType
.
JSON_PATH
);
...
...
This diff is collapsed.
Click to expand it.
backend/src/main/java/io/metersphere/api/dto/definition/request/assertions/MsAssertions.java
+
6
-
1
View file @
c5d58d40
...
...
@@ -96,7 +96,12 @@ public class MsAssertions extends MsTestElement {
assertion
.
setJsonValidationBool
(
true
);
assertion
.
setExpectNull
(
false
);
assertion
.
setInvert
(
false
);
assertion
.
setIsRegex
(
true
);
assertion
.
setProperty
(
"ASS_OPTION"
,
assertionJsonPath
.
getOption
());
if
(
StringUtils
.
isEmpty
(
assertionJsonPath
.
getOption
())
||
"REGEX"
.
equals
(
assertionJsonPath
.
getOption
()))
{
assertion
.
setIsRegex
(
true
);
}
else
{
assertion
.
setIsRegex
(
false
);
}
return
assertion
;
}
...
...
This diff is collapsed.
Click to expand it.
backend/src/main/java/io/metersphere/xmind/XmindCaseParser.java
+
2
-
2
View file @
c5d58d40
...
...
@@ -136,8 +136,8 @@ public class XmindCaseParser {
data
.
setNodePath
(
nodePath
);
if
(
data
.
getName
().
length
()
>
5
0
)
{
process
.
add
(
Translator
.
get
(
"test_case"
)
+
Translator
.
get
(
"test_track.length_less_than"
)
+
"
5
0"
,
nodePath
+
data
.
getName
());
if
(
data
.
getName
().
length
()
>
20
0
)
{
process
.
add
(
Translator
.
get
(
"test_case"
)
+
Translator
.
get
(
"test_track.length_less_than"
)
+
"
20
0"
,
nodePath
+
data
.
getName
());
}
if
(!
StringUtils
.
isEmpty
(
nodePath
))
{
...
...
This diff is collapsed.
Click to expand it.
backend/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java
0 → 100644
+
252
-
0
View file @
c5d58d40
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package
org.apache.jmeter.assertions
;
import
java.io.Serializable
;
import
java.text.DecimalFormat
;
import
java.util.Map
;
import
com.alibaba.fastjson.JSONArray
;
import
com.alibaba.fastjson.JSONObject
;
import
com.jayway.jsonpath.JsonPath
;
import
org.apache.commons.lang3.StringUtils
;
import
org.apache.jmeter.samplers.SampleResult
;
import
org.apache.jmeter.testelement.AbstractTestElement
;
import
org.apache.jmeter.testelement.ThreadListener
;
import
org.apache.jmeter.util.JMeterUtils
;
import
org.apache.oro.text.regex.Pattern
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
/**
* This is main class for JSONPath Assertion which verifies assertion on
* previous sample result using JSON path expression
*
* @since 4.0
*/
public
class
JSONPathAssertion
extends
AbstractTestElement
implements
Serializable
,
Assertion
,
ThreadListener
{
private
static
final
Logger
log
=
LoggerFactory
.
getLogger
(
JSONPathAssertion
.
class
);
private
static
final
long
serialVersionUID
=
2L
;
public
static
final
String
JSONPATH
=
"JSON_PATH"
;
public
static
final
String
EXPECTEDVALUE
=
"EXPECTED_VALUE"
;
public
static
final
String
JSONVALIDATION
=
"JSONVALIDATION"
;
public
static
final
String
EXPECT_NULL
=
"EXPECT_NULL"
;
public
static
final
String
INVERT
=
"INVERT"
;
public
static
final
String
ISREGEX
=
"ISREGEX"
;
private
static
ThreadLocal
<
DecimalFormat
>
decimalFormatter
=
ThreadLocal
.
withInitial
(
JSONPathAssertion:
:
createDecimalFormat
);
public
String
getOption
()
{
return
getPropertyAsString
(
"ASS_OPTION"
);
}
private
static
DecimalFormat
createDecimalFormat
()
{
DecimalFormat
decimalFormatter
=
new
DecimalFormat
(
"#.#"
);
decimalFormatter
.
setMaximumFractionDigits
(
340
);
// java.text.DecimalFormat.DOUBLE_FRACTION_DIGITS == 340
decimalFormatter
.
setMinimumFractionDigits
(
1
);
return
decimalFormatter
;
}
public
String
getJsonPath
()
{
return
getPropertyAsString
(
JSONPATH
);
}
public
void
setJsonPath
(
String
jsonPath
)
{
setProperty
(
JSONPATH
,
jsonPath
);
}
public
String
getExpectedValue
()
{
return
getPropertyAsString
(
EXPECTEDVALUE
);
}
public
void
setExpectedValue
(
String
expectedValue
)
{
setProperty
(
EXPECTEDVALUE
,
expectedValue
);
}
public
void
setJsonValidationBool
(
boolean
jsonValidation
)
{
setProperty
(
JSONVALIDATION
,
jsonValidation
);
}
public
void
setExpectNull
(
boolean
val
)
{
setProperty
(
EXPECT_NULL
,
val
);
}
public
boolean
isExpectNull
()
{
return
getPropertyAsBoolean
(
EXPECT_NULL
);
}
public
boolean
isJsonValidationBool
()
{
return
getPropertyAsBoolean
(
JSONVALIDATION
);
}
public
void
setInvert
(
boolean
invert
)
{
setProperty
(
INVERT
,
invert
);
}
public
boolean
isInvert
()
{
return
getPropertyAsBoolean
(
INVERT
);
}
public
void
setIsRegex
(
boolean
flag
)
{
setProperty
(
ISREGEX
,
flag
);
}
public
boolean
isUseRegex
()
{
return
getPropertyAsBoolean
(
ISREGEX
,
true
);
}
private
void
doAssert
(
String
jsonString
)
{
Object
value
=
JsonPath
.
read
(
jsonString
,
getJsonPath
());
if
(!
isJsonValidationBool
())
{
return
;
}
if
(
value
instanceof
JSONArray
)
{
if
(
arrayMatched
((
JSONArray
)
value
))
{
return
;
}
}
else
{
if
((
isExpectNull
()
&&
value
==
null
)
||
isEquals
(
value
))
{
return
;
}
}
if
(
isExpectNull
())
{
throw
new
IllegalStateException
(
String
.
format
(
"Value expected to be null, but found '%s'"
,
value
));
}
else
{
String
msg
;
if
(
isUseRegex
())
{
msg
=
"Value expected to match regexp '%s', but it did not match: '%s'"
;
}
else
{
msg
=
"Value expected to be '%s', but found '%s'"
;
}
throw
new
IllegalStateException
(
String
.
format
(
msg
,
getExpectedValue
(),
objectToString
(
value
)));
}
}
private
boolean
arrayMatched
(
JSONArray
value
)
{
if
(
value
.
isEmpty
()
&&
"[]"
.
equals
(
getExpectedValue
()))
{
return
true
;
}
for
(
Object
subj
:
value
.
toArray
())
{
if
((
subj
==
null
&&
isExpectNull
())
||
isEquals
(
subj
))
{
return
true
;
}
}
return
isEquals
(
value
);
}
private
boolean
isEquals
(
Object
subj
)
{
String
str
=
objectToString
(
subj
);
if
(
isUseRegex
())
{
Pattern
pattern
=
JMeterUtils
.
getPatternCache
().
getPattern
(
getExpectedValue
());
return
JMeterUtils
.
getMatcher
().
matches
(
str
,
pattern
);
}
else
{
if
(
StringUtils
.
isNotEmpty
(
getOption
()))
{
boolean
refFlag
=
false
;
switch
(
getOption
())
{
case
"CONTAINS"
:
refFlag
=
str
.
contains
(
getExpectedValue
());
break
;
case
"NOT_CONTAINS"
:
refFlag
=
!
str
.
contains
(
getExpectedValue
());
break
;
case
"EQUALS"
:
refFlag
=
str
.
equals
(
getExpectedValue
());
break
;
case
"NOT_EQUALS"
:
refFlag
=
!
str
.
contains
(
getExpectedValue
());
break
;
}
return
refFlag
;
}
return
str
.
equals
(
getExpectedValue
());
}
}
@Override
public
AssertionResult
getResult
(
SampleResult
samplerResult
)
{
AssertionResult
result
=
new
AssertionResult
(
getName
());
String
responseData
=
samplerResult
.
getResponseDataAsString
();
if
(
responseData
.
isEmpty
())
{
return
result
.
setResultForNull
();
}
result
.
setFailure
(
false
);
result
.
setFailureMessage
(
""
);
if
(!
isInvert
())
{
try
{
doAssert
(
responseData
);
}
catch
(
Exception
e
)
{
log
.
debug
(
"Assertion failed"
,
e
);
result
.
setFailure
(
true
);
result
.
setFailureMessage
(
e
.
getMessage
());
}
}
else
{
try
{
doAssert
(
responseData
);
result
.
setFailure
(
true
);
if
(
isJsonValidationBool
())
{
if
(
isExpectNull
())
{
result
.
setFailureMessage
(
"Failed that JSONPath "
+
getJsonPath
()
+
" not matches null"
);
}
else
{
result
.
setFailureMessage
(
"Failed that JSONPath "
+
getJsonPath
()
+
" not matches "
+
getExpectedValue
());
}
}
else
{
result
.
setFailureMessage
(
"Failed that JSONPath not exists: "
+
getJsonPath
());
}
}
catch
(
Exception
e
)
{
log
.
debug
(
"Assertion failed, as expected"
,
e
);
}
}
return
result
;
}
public
static
String
objectToString
(
Object
subj
)
{
String
str
;
if
(
subj
==
null
)
{
str
=
"null"
;
}
else
if
(
subj
instanceof
Map
)
{
//noinspection unchecked
str
=
new
JSONObject
((
Map
<
String
,
Object
>)
subj
).
toJSONString
();
}
else
if
(
subj
instanceof
Double
||
subj
instanceof
Float
)
{
str
=
decimalFormatter
.
get
().
format
(
subj
);
}
else
{
str
=
subj
.
toString
();
}
return
str
;
}
@Override
public
void
threadStarted
()
{
// nothing to do on thread start
}
@Override
public
void
threadFinished
()
{
decimalFormatter
.
remove
();
}
}
This diff is collapsed.
Click to expand it.
frontend/src/business/components/api/definition/components/assertion/ApiAssertionJsonPath.vue
+
26
-
2
View file @
c5d58d40
<
template
>
<div>
<div
v-loading=
"loading"
>
<el-row
:gutter=
"10"
type=
"flex"
justify=
"space-between"
align=
"middle"
>
<el-col>
<el-input
:disabled=
"isReadOnly"
v-model=
"jsonPath.expression"
maxlength=
"200"
size=
"small"
show-word-limit
:placeholder=
"$t('api_test.request.extract.json_path_expression')"
/>
</el-col>
<el-col>
<el-select
v-model=
"jsonPath.option"
class=
"ms-col-type"
size=
"small"
style=
"width:40%;margin-right: 10px"
@
change=
"reload"
>
<el-option
:label=
"$t('api_test.request.assertions.contains')"
value=
"CONTAINS"
/>
<el-option
:label=
"$t('api_test.request.assertions.not_contains')"
value=
"NOT_CONTAINS"
/>
<el-option
:label=
"$t('api_test.request.assertions.equals')"
value=
"EQUALS"
/>
<el-option
:label=
"$t('commons.adv_search.operators.not_equals')"
value=
"NOT_EQUALS"
/>
<el-option
label=
"正则匹配"
value=
"REGEX"
/>
</el-select>
<el-input
:disabled=
"isReadOnly"
v-model=
"jsonPath.expect"
size=
"small"
show-word-limit
:placeholder=
"$t('api_test.request.assertions.expect')"
/>
:placeholder=
"$t('api_test.request.assertions.expect')"
style=
"width: 50%"
/>
<el-tooltip
placement=
"top"
v-if=
"jsonPath.option === 'REGEX'"
>
<div
slot=
"content"
>
特殊字符"$ ( ) * + . [ ] \ ^ { } |"需转义为"\ "+"特殊字符",如"\$"
</div>
<i
class=
"el-icon-question"
style=
"cursor: pointer"
/>
</el-tooltip>
</el-col>
<el-col
class=
"assertion-btn"
>
<el-button
:disabled=
"isReadOnly"
type=
"danger"
size=
"mini"
icon=
"el-icon-delete"
circle
@
click=
"remove"
v-if=
"edit"
/>
...
...
@@ -44,8 +55,15 @@
}
},
created
()
{
if
(
!
this
.
jsonPath
.
option
)
{
this
.
jsonPath
.
option
=
"
REGEX
"
;
}
},
data
()
{
return
{
loading
:
false
}
},
...
...
@@ -71,6 +89,12 @@
jsonPath
.
description
=
jsonPath
.
expression
+
"
expect:
"
+
(
jsonPath
.
expect
?
jsonPath
.
expect
:
''
);
return
jsonPath
;
},
reload
()
{
this
.
loading
=
true
this
.
$nextTick
(()
=>
{
this
.
loading
=
false
})
},
setJSONPathDescription
()
{
this
.
jsonPath
.
description
=
this
.
jsonPath
.
expression
+
"
expect:
"
+
(
this
.
jsonPath
.
expect
?
this
.
jsonPath
.
expect
:
''
);
}
...
...
This diff is collapsed.
Click to expand it.
frontend/src/business/components/api/definition/components/assertion/ApiAssertions.vue
+
29
-
24
View file @
c5d58d40
...
...
@@ -56,31 +56,31 @@
</
template
>
<
script
>
import
MsApiAssertionText
from
"
./ApiAssertionText
"
;
import
MsApiAssertionRegex
from
"
./ApiAssertionRegex
"
;
import
MsApiAssertionDuration
from
"
./ApiAssertionDuration
"
;
import
{
ASSERTION_TYPE
,
JSONPath
}
from
"
../../model/ApiTestModel
"
;
import
MsApiAssertionsEdit
from
"
./ApiAssertionsEdit
"
;
import
MsApiAssertionJsonPath
from
"
./ApiAssertionJsonPath
"
;
import
MsApiAssertionJsr223
from
"
./ApiAssertionJsr223
"
;
import
MsApiJsonpathSuggestList
from
"
./ApiJsonpathSuggestList
"
;
import
MsApiAssertionXPath2
from
"
./ApiAssertionXPath2
"
;
import
{
getUUID
}
from
"
@/common/js/utils
"
;
import
ApiJsonPathSuggestButton
from
"
./ApiJsonPathSuggestButton
"
;
import
MsApiJsonpathSuggest
from
"
./ApiJsonpathSuggest
"
;
import
ApiBaseComponent
from
"
../../../automation/scenario/common/ApiBaseComponent
"
;
import
MsApiAssertionText
from
"
./ApiAssertionText
"
;
import
MsApiAssertionRegex
from
"
./ApiAssertionRegex
"
;
import
MsApiAssertionDuration
from
"
./ApiAssertionDuration
"
;
import
{
ASSERTION_TYPE
,
JSONPath
}
from
"
../../model/ApiTestModel
"
;
import
MsApiAssertionsEdit
from
"
./ApiAssertionsEdit
"
;
import
MsApiAssertionJsonPath
from
"
./ApiAssertionJsonPath
"
;
import
MsApiAssertionJsr223
from
"
./ApiAssertionJsr223
"
;
import
MsApiJsonpathSuggestList
from
"
./ApiJsonpathSuggestList
"
;
import
MsApiAssertionXPath2
from
"
./ApiAssertionXPath2
"
;
import
{
getUUID
}
from
"
@/common/js/utils
"
;
import
ApiJsonPathSuggestButton
from
"
./ApiJsonPathSuggestButton
"
;
import
MsApiJsonpathSuggest
from
"
./ApiJsonpathSuggest
"
;
import
ApiBaseComponent
from
"
../../../automation/scenario/common/ApiBaseComponent
"
;
export
default
{
name
:
"
MsApiAssertions
"
,
components
:
{
ApiBaseComponent
,
MsApiJsonpathSuggest
,
ApiJsonPathSuggestButton
,
MsApiAssertionXPath2
,
MsApiAssertionJsr223
,
MsApiJsonpathSuggestList
,
MsApiAssertionJsonPath
,
MsApiAssertionsEdit
,
MsApiAssertionDuration
,
MsApiAssertionRegex
,
MsApiAssertionText
export
default
{
name
:
"
MsApiAssertions
"
,
components
:
{
ApiBaseComponent
,
MsApiJsonpathSuggest
,
ApiJsonPathSuggestButton
,
MsApiAssertionXPath2
,
MsApiAssertionJsr223
,
MsApiJsonpathSuggestList
,
MsApiAssertionJsonPath
,
MsApiAssertionsEdit
,
MsApiAssertionDuration
,
MsApiAssertionRegex
,
MsApiAssertionText
},
props
:
{
draggable
:
{
...
...
@@ -146,6 +146,11 @@ export default {
jsonItem
.
expression
=
data
.
path
;
jsonItem
.
expect
=
data
.
value
;
jsonItem
.
setJSONPathDescription
();
let
expect
=
jsonItem
.
expect
.
replaceAll
(
'
\\
'
,
"
\\\\
"
).
replaceAll
(
'
(
'
,
"
\\
(
"
).
replaceAll
(
'
)
'
,
"
\\
)
"
)
.
replaceAll
(
'
+
'
,
"
\\
+
"
).
replaceAll
(
'
.
'
,
"
\\
.
"
).
replaceAll
(
'
[
'
,
"
\\
[
"
).
replaceAll
(
'
]
'
,
"
\\
]
"
)
.
replaceAll
(
'
?
'
,
"
\\
?
"
).
replaceAll
(
'
/
'
,
"
\\
/
"
).
replaceAll
(
'
*
'
,
"
\\
*
"
)
.
replaceAll
(
'
^
'
,
"
\\
^
"
).
replaceAll
(
'
{
'
,
"
\\
{
"
).
replaceAll
(
'
}
'
,
"
\\
}
"
).
replaceAll
(
'
$
'
,
"
\\
$
"
);
jsonItem
.
expect
=
expect
;
this
.
assertions
.
jsonPath
.
push
(
jsonItem
);
},
clearJson
()
{
...
...
This diff is collapsed.
Click to expand it.
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment