aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/main/java/org/onap/clamp/loop/Loop.java11
-rw-r--r--src/main/java/org/onap/clamp/loop/LoopController.java12
-rw-r--r--src/main/java/org/onap/clamp/loop/LoopService.java26
-rw-r--r--src/main/resources/clds/camel/rest/clamp-api-v2.xml30
-rw-r--r--src/test/java/org/onap/clamp/loop/LoopControllerTestItCase.java34
-rw-r--r--ui-react/src/api/LoopService.js67
-rw-r--r--ui-react/src/components/dialogs/Loop/ModifyLoopModal.js98
-rw-r--r--ui-react/src/components/dialogs/Loop/ModifyLoopModal.test.js109
8 files changed, 336 insertions, 51 deletions
diff --git a/src/main/java/org/onap/clamp/loop/Loop.java b/src/main/java/org/onap/clamp/loop/Loop.java
index f185460c..2bf3decd 100644
--- a/src/main/java/org/onap/clamp/loop/Loop.java
+++ b/src/main/java/org/onap/clamp/loop/Loop.java
@@ -271,6 +271,17 @@ public class Loop extends AuditEntity implements Serializable {
}
/**
+ * This method removes an operational policy to the loop.
+ * It re-computes the Svg as well.
+ *
+ * @param opPolicy the operationalPolicy to add
+ */
+ public void removeOperationalPolicy(OperationalPolicy opPolicy) {
+ operationalPolicies.remove(opPolicy);
+ this.setSvgRepresentation(SvgLoopGenerator.getSvgImage(this));
+ }
+
+ /**
* This method adds an micro service policy to the loop.
* It re-computes the Svg as well.
*
diff --git a/src/main/java/org/onap/clamp/loop/LoopController.java b/src/main/java/org/onap/clamp/loop/LoopController.java
index 1a67455e..1a4ae599 100644
--- a/src/main/java/org/onap/clamp/loop/LoopController.java
+++ b/src/main/java/org/onap/clamp/loop/LoopController.java
@@ -110,6 +110,18 @@ public class LoopController {
}
/**
+ * This method remove an operational policy to a loop instance.
+ *
+ * @param loopName The loop name
+ * @param policyType The policy model type
+ * @param policyVersion The policy model version
+ * @return The loop modified
+ */
+ public Loop removeOperationalPolicy(String loopName, String policyType, String policyVersion) {
+ return loopService.removeOperationalPolicy(loopName, policyType, policyVersion);
+ }
+
+ /**
* This method deletes the loop.
*
* @param loopName The loop Name
diff --git a/src/main/java/org/onap/clamp/loop/LoopService.java b/src/main/java/org/onap/clamp/loop/LoopService.java
index 98a2fbdd..953a5947 100644
--- a/src/main/java/org/onap/clamp/loop/LoopService.java
+++ b/src/main/java/org/onap/clamp/loop/LoopService.java
@@ -120,7 +120,31 @@ public class LoopService {
new OperationalPolicy(Policy.generatePolicyName("OPERATIONAL", loop.getModelService().getName(),
loop.getModelService().getVersion(), RandomStringUtils.randomAlphanumeric(3),
RandomStringUtils.randomAlphanumeric(4)), loop, null, policyModel, null, null, null));
- return loopsRepository.save(loop);
+ return loopsRepository.saveAndFlush(loop);
+ }
+
+ /**
+ * This method remove an operational policy to a loop instance.
+ *
+ * @param loopName The loop name
+ * @param policyType The policy model type
+ * @param policyVersion The policy model version
+ * @return The loop modified
+ */
+ Loop removeOperationalPolicy(String loopName, String policyType, String policyVersion) {
+ Loop loop = getLoop(loopName);
+ PolicyModel policyModel = policyModelsService.getPolicyModel(policyType, policyVersion);
+ if (policyModel == null) {
+ return null;
+ }
+ for (OperationalPolicy opPolicy : loop.getOperationalPolicies()) {
+ if (opPolicy.getPolicyModel().getPolicyModelType().equals(policyType) &&
+ opPolicy.getPolicyModel().getVersion().equals(policyVersion)) {
+ loop.removeOperationalPolicy(opPolicy);
+ break;
+ }
+ }
+ return loopsRepository.saveAndFlush(loop);
}
Loop updateAndSaveOperationalPolicies(String loopName, List<OperationalPolicy> newOperationalPolicies) {
diff --git a/src/main/resources/clds/camel/rest/clamp-api-v2.xml b/src/main/resources/clds/camel/rest/clamp-api-v2.xml
index fbf90712..d1c191dc 100644
--- a/src/main/resources/clds/camel/rest/clamp-api-v2.xml
+++ b/src/main/resources/clds/camel/rest/clamp-api-v2.xml
@@ -604,6 +604,36 @@
</doTry>
</route>
</put>
+ <put uri="/v2/loop/removeOperationaPolicy/{loopName}/policyModel/{policyType}/{policyVersion}" outType="org.onap.clamp.loop.Loop" produces="application/json">
+ <route>
+ <removeHeaders pattern="*" excludePattern="loopName|policyType|policyVersion" />
+ <doTry>
+ <to
+ uri="bean:org.onap.clamp.flow.log.FlowLogOperation?method=startLog(*, 'REMOVE operational Policy')" />
+ <to
+ uri="bean:org.onap.clamp.authorization.AuthorizationController?method=authorize(*,'cl','','update')" />
+ <to uri="direct:load-loop" />
+ <to
+ uri="bean:org.onap.clamp.loop.LoopController?method=removeOperationalPolicy(${header.loopName},${header.policyType},${header.policyVersion})" />
+ <to
+ uri="bean:org.onap.clamp.loop.log.LoopLogService?method=addLog('REMOVE OperationalPolicy request successfully executed','INFO',${exchangeProperty[loopObject]})" />
+ <to
+ uri="bean:org.onap.clamp.flow.log.FlowLogOperation?method=endLog()" />
+ <doCatch>
+ <exception>java.lang.Exception</exception>
+ <handled>
+ <constant>false</constant>
+ </handled>
+ <to
+ uri="bean:org.onap.clamp.flow.log.FlowLogOperation?method=errorLog()" />
+ <log loggingLevel="ERROR"
+ message="REMOVE OperationalPolicy request failed for loop: ${header.loopName}" />
+ <to
+ uri="bean:org.onap.clamp.loop.log.LoopLogService?method=addLog('REMOVE OperationalPolicy request failed','ERROR',${exchangeProperty[loopObject]})" />
+ </doCatch>
+ </doTry>
+ </route>
+ </put>
<post
uri="/v2/loop/create/{loopName}?templateName={templateName}"
outType="org.onap.clamp.loop.Loop" consumes="application/json"
diff --git a/src/test/java/org/onap/clamp/loop/LoopControllerTestItCase.java b/src/test/java/org/onap/clamp/loop/LoopControllerTestItCase.java
index f017dd89..24a9037a 100644
--- a/src/test/java/org/onap/clamp/loop/LoopControllerTestItCase.java
+++ b/src/test/java/org/onap/clamp/loop/LoopControllerTestItCase.java
@@ -36,14 +36,17 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.onap.clamp.clds.Application;
import org.onap.clamp.clds.util.JsonUtils;
+import org.onap.clamp.loop.service.Service;
import org.onap.clamp.loop.template.LoopTemplate;
import org.onap.clamp.loop.template.PolicyModel;
import org.onap.clamp.loop.template.PolicyModelsService;
import org.onap.clamp.policy.microservice.MicroServicePolicy;
import org.onap.clamp.policy.microservice.MicroServicePolicyService;
import org.onap.clamp.policy.operational.OperationalPolicy;
+import org.onap.clamp.policy.operational.OperationalPolicyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.Commit;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@@ -63,6 +66,9 @@ public class LoopControllerTestItCase {
MicroServicePolicyService microServicePolicyService;
@Autowired
+ OperationalPolicyService operationalPolicyService;
+
+ @Autowired
PolicyModelsService policyModelsService;
@Autowired
@@ -74,6 +80,8 @@ public class LoopControllerTestItCase {
LoopTemplate template = new LoopTemplate();
template.setName("testTemplate");
testLoop.setLoopTemplate(template);
+ Service modelService = new Service("{\"name\":\"serviceName\",\"UUID\":\"uuid\"}","{}");
+ testLoop.setModelService(modelService);
loopService.saveOrUpdateLoop(testLoop);
}
@@ -131,7 +139,7 @@ public class LoopControllerTestItCase {
@Transactional
public void testUpdateMicroservicePolicy() {
saveTestLoopToDb();
- PolicyModel policyModel = new PolicyModel("",
+ PolicyModel policyModel = new PolicyModel("testPolicyModel",
"tosca_definitions_version: tosca_simple_yaml_1_0_0","1.0.0");
policyModelsService.saveOrUpdatePolicyModel(policyModel);
MicroServicePolicy policy = new MicroServicePolicy("policyName", policyModel, false,
@@ -147,4 +155,28 @@ public class LoopControllerTestItCase {
String svgRepresentation = loopController.getSvgRepresentation(EXAMPLE_LOOP_NAME);
assertThat(svgRepresentation).isEqualTo("representation");
}
+
+ @Test
+ @Transactional
+ public void testAddAndRemoveOperationalPolicies() {
+ saveTestLoopToDb();
+ PolicyModel policyModel = new PolicyModel("testPolicyModel",
+ "tosca_definitions_version: tosca_simple_yaml_1_0_0","1.0.0");
+ policyModelsService.saveOrUpdatePolicyModel(policyModel);
+
+ loopController.addOperationalPolicy(EXAMPLE_LOOP_NAME, "testPolicyModel", "1.0.0");
+
+ Loop newLoop = loopController.getLoop(EXAMPLE_LOOP_NAME);
+ Set<OperationalPolicy> opPolicyList = newLoop.getOperationalPolicies();
+ assertThat(opPolicyList.size()).isEqualTo(1);
+ for(OperationalPolicy policy : opPolicyList) {
+ assertThat(policy.getName().contains("OPERATIONAL_serviceName")).isTrue();
+ assertThat(policy.getPolicyModel().getPolicyModelType()).isEqualTo("testPolicyModel");
+ assertThat(policy.getPolicyModel().getVersion()).isEqualTo("1.0.0");
+ }
+
+ loopController.removeOperationalPolicy(EXAMPLE_LOOP_NAME, "testPolicyModel", "1.0.0");
+ Loop newLoop2 = loopController.getLoop(EXAMPLE_LOOP_NAME);
+ assertThat(newLoop2.getOperationalPolicies().size()).isEqualTo(0);
+ }
} \ No newline at end of file
diff --git a/ui-react/src/api/LoopService.js b/ui-react/src/api/LoopService.js
index d665f814..2750763d 100644
--- a/ui-react/src/api/LoopService.js
+++ b/ui-react/src/api/LoopService.js
@@ -194,26 +194,49 @@ export default class LoopService {
});
}
- static addOperationalPolicyType(loopName, policyType, policyVersion) {
- return fetch('/restservices/clds/v2/loop/addOperationaPolicy/' + loopName + '/policyModel/' + policyType +'/' + policyVersion , {
- method: 'PUT',
- headers: {
- "Content-Type": "application/json"
- },
- credentials: 'same-origin'
- })
- .then(function (response) {
- console.debug("Add Operational Policy response received: ", response.status);
- if (response.ok) {
- return response.json();
- } else {
- console.error("Add Operational Policy query failed");
- return {};
- }
- })
- .catch(function (error) {
- console.error("Add Operational Policy error received", error);
- return {};
- });
- }
+ static addOperationalPolicyType(loopName, policyType, policyVersion) {
+ return fetch('/restservices/clds/v2/loop/addOperationaPolicy/' + loopName + '/policyModel/' + policyType +'/' + policyVersion , {
+ method: 'PUT',
+ headers: {
+ "Content-Type": "application/json"
+ },
+ credentials: 'same-origin'
+ })
+ .then(function (response) {
+ console.debug("Add Operational Policy response received: ", response.status);
+ if (response.ok) {
+ return response.json();
+ } else {
+ console.error("Add Operational Policy query failed");
+ return {};
+ }
+ })
+ .catch(function (error) {
+ console.error("Add Operational Policy error received", error);
+ return {};
+ });
+ }
+
+ static removeOperationalPolicyType(loopName, policyType, policyVersion) {
+ return fetch('/restservices/clds/v2/loop/removeOperationaPolicy/' + loopName + '/policyModel/' + policyType +'/' + policyVersion , {
+ method: 'PUT',
+ headers: {
+ "Content-Type": "application/json"
+ },
+ credentials: 'same-origin'
+ })
+ .then(function (response) {
+ console.debug("Remove Operational Policy response received: ", response.status);
+ if (response.ok) {
+ return response.json();
+ } else {
+ console.error("Remove Operational Policy query failed");
+ return {};
+ }
+ })
+ .catch(function (error) {
+ console.error("Remove Operational Policy error received", error);
+ return {};
+ });
+ }
}
diff --git a/ui-react/src/components/dialogs/Loop/ModifyLoopModal.js b/ui-react/src/components/dialogs/Loop/ModifyLoopModal.js
index 4cd21fdb..7c16b786 100644
--- a/ui-react/src/components/dialogs/Loop/ModifyLoopModal.js
+++ b/ui-react/src/components/dialogs/Loop/ModifyLoopModal.js
@@ -35,6 +35,8 @@ import FirstPage from '@material-ui/icons/FirstPage';
import LastPage from '@material-ui/icons/LastPage';
import Search from '@material-ui/icons/Search';
import LoopService from '../../../api/LoopService';
+import Tabs from 'react-bootstrap/Tabs';
+import Tab from 'react-bootstrap/Tab';
const ModalStyled = styled(Modal)`
@@ -61,6 +63,8 @@ export default class ModifyLoopModal extends React.Component {
content: 'Please select Tosca model to view the details',
selectedRowData: {},
toscaPolicyModelsData: [],
+ selectedPolicyModelsData: [],
+ key: 'add',
toscaColumns: [
{ title: "#", field: "index", render: rowData => rowData.tableData.id + 1,
cellStyle: cellStyle,
@@ -105,11 +109,12 @@ export default class ModifyLoopModal extends React.Component {
constructor(props, context) {
super(props, context);
this.handleClose = this.handleClose.bind(this);
- this.getPolicyToscaModels = this.getToscaPolicyModels.bind(this);
+ this.initializeToscaPolicyModelsInfo = this.initializeToscaPolicyModelsInfo.bind(this);
this.handleYamlContent = this.handleYamlContent.bind(this);
this.getToscaPolicyModelYaml = this.getToscaPolicyModelYaml.bind(this);
this.handleAdd = this.handleAdd.bind(this);
- this.getToscaPolicyModels();
+ this.handleRemove = this.handleRemove.bind(this);
+ this.initializeToscaPolicyModelsInfo();
}
componentWillReceiveProps(newProps) {
@@ -119,9 +124,16 @@ export default class ModifyLoopModal extends React.Component {
});
}
- getToscaPolicyModels() {
- PolicyToscaService.getToscaPolicyModels().then(allToscaModels => {
- this.setState({ toscaPolicyModelsData: allToscaModels});
+ initializeToscaPolicyModelsInfo() {
+ var operationalPolicies = this.state.loopCache.getOperationalPolicies();
+ var selectedPolicyModels = [];
+ for (var policy in operationalPolicies) {
+ selectedPolicyModels.push(operationalPolicies[policy]["policyModel"]);
+ }
+
+ PolicyToscaService.getToscaPolicyModels().then(allToscaModels => {
+ this.setState({ toscaPolicyModelsData: allToscaModels,
+ selectedPolicyModelsData: selectedPolicyModels});
});
}
@@ -149,38 +161,70 @@ export default class ModifyLoopModal extends React.Component {
}
handleAdd() {
- LoopService.addOperationalPolicyType(this.state.loopCache.getLoopName(),this.state.selectedRowData.policyModelType,this.state.selectedRowData.version);
- this.handleClose();
- this.props.loadLoopFunction(this.state.loopCache.getLoopName());
+ LoopService.addOperationalPolicyType(this.state.loopCache.getLoopName(),this.state.selectedRowData.policyModelType,this.state.selectedRowData.version);
+ this.handleClose();
+ this.props.loadLoopFunction(this.state.loopCache.getLoopName());
+ }
+
+ handleRemove() {
+ LoopService.removeOperationalPolicyType(this.state.loopCache.getLoopName(),this.state.selectedRowData.policyModelType,this.state.selectedRowData.version);
+ this.handleClose();
+ this.props.loadLoopFunction(this.state.loopCache.getLoopName());
}
render() {
return (
<ModalStyled size="xl" show={this.state.show} onHide={this.handleClose}>
<Modal.Header closeButton>
+ <Modal.Title>Modify Loop Operational Policies</Modal.Title>
</Modal.Header>
- <Modal.Body>
- <MaterialTable
- title={"View Tosca Policy Models"}
- data={this.state.toscaPolicyModelsData}
- columns={this.state.toscaColumns}
- icons={this.state.tableIcons}
- onRowClick={(event, rowData) => {this.getToscaPolicyModelYaml(rowData.policyModelType, rowData.version);this.setState({selectedRowData: rowData})}}
- options={{
- headerStyle: rowHeaderStyle,
- rowStyle: rowData => ({
- backgroundColor: (this.state.selectedRowData !== {} && this.state.selectedRowData.id === rowData.tableData.id) ? '#EEE' : '#FFF'
- })
- }}
- />
- <div>
- <TextModal value={this.state.content} onChange={this.handleYamlContent}/>
- </div>
- </Modal.Body>
+ <Tabs id="controlled-tab-example" activeKey={this.state.key} onSelect={key => this.setState({ key, selectedRowData: {} })}>
+ <Tab eventKey="add" title="Add Operational Policies">
+ <Modal.Body>
+ <MaterialTable
+ title={"View Tosca Policy Models"}
+ data={this.state.toscaPolicyModelsData}
+ columns={this.state.toscaColumns}
+ icons={this.state.tableIcons}
+ onRowClick={(event, rowData) => {this.getToscaPolicyModelYaml(rowData.policyModelType, rowData.version);this.setState({selectedRowData: rowData})}}
+ options={{
+ headerStyle: rowHeaderStyle,
+ rowStyle: rowData => ({
+ backgroundColor: (this.state.selectedRowData !== {} && this.state.selectedRowData.tableData !== undefined
+ && this.state.selectedRowData.tableData.id === rowData.tableData.id) ? '#EEE' : '#FFF'
+ })
+ }}
+ />
+ <div>
+ <TextModal value={this.state.content} onChange={this.handleYamlContent}/>
+ </div>
+ </Modal.Body>
+ </Tab>
+ <Tab eventKey="remove" title="Remove Operational Policies">
+ <Modal.Body>
+ <MaterialTable
+ title={"Already added Tosca Policy Models"}
+ data={this.state.selectedPolicyModelsData}
+ columns={this.state.toscaColumns}
+ icons={this.state.tableIcons}
+ onRowClick={(event, rowData) => {this.setState({selectedRowData: rowData})}}
+ options={{
+ headerStyle: rowHeaderStyle,
+ rowStyle: rowData => ({
+ backgroundColor: (this.state.selectedRowData !== {} && this.state.selectedRowData.tableData !== undefined
+ && this.state.selectedRowData.tableData.id === rowData.tableData.id) ? '#EEE' : '#FFF'
+ })
+ }}
+ />
+ </Modal.Body>
+ </Tab>
+ </Tabs>
<Modal.Footer>
<Button variant="secondary" type="null" onClick={this.handleClose}>Cancel</Button>
- <Button variant="primary" type="submit" onClick={this.handleAdd}>Add</Button>
+ <Button variant="primary" disabled={(this.state.key === "remove")} type="submit" onClick={this.handleAdd}>Add</Button>
+ <Button variant="primary" disabled={(this.state.key === "add")} type="submit" onClick={this.handleRemove}>Remove</Button>
</Modal.Footer>
+
</ModalStyled>
);
}
diff --git a/ui-react/src/components/dialogs/Loop/ModifyLoopModal.test.js b/ui-react/src/components/dialogs/Loop/ModifyLoopModal.test.js
new file mode 100644
index 00000000..055ad0e6
--- /dev/null
+++ b/ui-react/src/components/dialogs/Loop/ModifyLoopModal.test.js
@@ -0,0 +1,109 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP CLAMP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights
+ * reserved.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END============================================
+ * ===================================================================
+ *
+ */
+import React from 'react';
+import { mount } from 'enzyme';
+import ModifyLoopModal from './ModifyLoopModal';
+import LoopCache from '../../../api/LoopCache';
+import LoopService from '../../../api/LoopService';
+import PolicyToscaService from '../../../api/PolicyToscaService';
+
+describe('Verify ModifyLoopModal', () => {
+ beforeEach(() => {
+ PolicyToscaService.getToscaPolicyModels = jest.fn().mockImplementation(() => {
+ return Promise.resolve([{
+ "policyModelType":"test",
+ "policyAcronym":"test",
+ "version":"1.0.0",
+ "updatedBy":"",
+ "updatedDate":""
+ }]);
+ });
+ PolicyToscaService.getToscaPolicyModelYaml = jest.fn().mockImplementation(() => {
+ return Promise.resolve("OK");
+ });
+ })
+
+ const loopCache = new LoopCache({
+ "name": "LOOP_Jbv1z_v1_0_ResourceInstanceName1_tca",
+ "microServicePolicies": [{
+ "name": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca",
+ "modelType": "onap.policies.monitoring.cdap.tca.hi.lo.app",
+ "properties": {"domain": "measurementsForVfScaling"},
+ "shared": false,
+ "jsonRepresentation": {"schema": {}}
+ }],
+ "globalPropertiesJson": {
+ "dcaeDeployParameters": {
+ "testMs": {
+ "location_id": "",
+ "policy_id": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca"
+ }
+ }
+ }
+ });
+ const historyMock = { push: jest.fn() };
+ const flushPromises = () => new Promise(setImmediate);
+
+ it('Test handleClose', () => {
+ const handleClose = jest.spyOn(ModifyLoopModal.prototype,'handleClose');
+ const component = mount(<ModifyLoopModal history={historyMock} loopCache={loopCache}/>)
+
+ component.find('[variant="secondary"]').get(0).props.onClick();
+
+ expect(handleClose).toHaveBeenCalledTimes(1);
+ expect(component.state('show')).toEqual(false);
+ expect(historyMock.push.mock.calls[0]).toEqual([ '/']);
+ });
+
+ it('Test getToscaPolicyModelYaml', async () => {
+ const flushPromises = () => new Promise(setImmediate);
+ const component = mount(<ModifyLoopModal history={historyMock} loopCache={loopCache}/>)
+ component.setState({
+ "selectedRowData": {"tableData":{"id":0}}
+ });
+ const instance = component.instance();
+
+ instance.getToscaPolicyModelYaml("","1.0.0");
+ expect(component.state('content')).toEqual("Please select Tosca model to view the details");
+
+ instance.getToscaPolicyModelYaml("test","1.0.0");
+ await flushPromises();
+ expect(component.state('content')).toEqual("OK");
+
+ PolicyToscaService.getToscaPolicyModelYaml = jest.fn().mockImplementation(() => {
+ return Promise.resolve("");
+ });
+ instance.getToscaPolicyModelYaml("test","1.0.0");
+ await flushPromises();
+ expect(component.state('content')).toEqual("No Tosca model Yaml available");
+ });
+
+ it('Test handleYamlContent', async () => {
+ const component = mount(<ModifyLoopModal loopCache={loopCache}/>)
+ const instance = component.instance();
+
+ const event = {"target":{"value":"testValue"}}
+ instance.handleYamlContent(event);
+ expect(component.state('content')).toEqual("testValue");
+ });
+}); \ No newline at end of file