changeset 491:b838d00420df

HttpResponseContentExtractorFilter
author Devel 1
date Fri, 11 Aug 2017 10:32:25 +0200
parents df8db8c1a760
children f7ad421d783b
files stress-tester/src/main/java/com/passus/st/client/http/filter/HttpResponseContentExtractorFilter.java stress-tester/src/main/java/com/passus/st/client/http/filter/HttpResponseContentExtractorFilterTransformer.java stress-tester/src/test/java/com/passus/st/client/http/filter/HttpFilterTestUtils.java stress-tester/src/test/java/com/passus/st/client/http/filter/HttpResponseContentExtractorFilterTest.java stress-tester/src/test/java/com/passus/st/client/http/filter/HttpScopeModificationFilterTest.java
diffstat 5 files changed, 494 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpResponseContentExtractorFilter.java	Fri Aug 11 10:32:25 2017 +0200
@@ -0,0 +1,197 @@
+package com.passus.st.client.http.filter;
+
+import com.passus.commons.Assert;
+import com.passus.commons.annotations.Plugin;
+import com.passus.config.Configuration;
+import com.passus.config.annotations.NodeDefinitionCreate;
+import static com.passus.config.schema.ConfigurationSchemaBuilder.mapDef;
+import static com.passus.config.schema.ConfigurationSchemaBuilder.mixedDef;
+import static com.passus.config.schema.ConfigurationSchemaBuilder.tupleDef;
+import static com.passus.config.schema.ConfigurationSchemaBuilder.valueDef;
+import static com.passus.config.schema.ConfigurationSchemaBuilder.valueDefBool;
+import com.passus.config.schema.KeyNameVaryListNodeDefinition;
+import com.passus.config.schema.MapNodeDefinition;
+import com.passus.config.schema.MixedNodeDefinition;
+import com.passus.config.schema.NodeDefinition;
+import com.passus.config.schema.NodeDefinitionCreator;
+import com.passus.net.http.HttpRequest;
+import com.passus.net.http.HttpResponse;
+import com.passus.st.ParametersBag;
+import com.passus.st.client.http.HttpFlowContext;
+import com.passus.st.client.http.extractor.ContentExtractor;
+import com.passus.st.config.HeaderOperationNodeDefinition;
+import com.passus.st.plugin.PluginConstants;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ *
+ * @author mikolaj.podbielski
+ */
+@Plugin(name = HttpResponseContentExtractorFilter.TYPE, category = PluginConstants.CATEGORY_HTTP_FILTER)
+@NodeDefinitionCreate(HttpResponseContentExtractorFilter.NodeDefCreator.class)
+public class HttpResponseContentExtractorFilter extends HttpFilter {
+
+    public static final String TYPE = "responseContentExtractor";
+
+    public static abstract class Operation {
+
+        public abstract void process(HttpResponse response, HttpFlowContext context);
+    }
+
+    public static abstract class SetScopeParamOperation extends Operation {
+
+        protected final String paramName;
+        protected final ContentExtractor contentExtractor;
+        protected final boolean checkValueExists;
+
+        public SetScopeParamOperation(String paramName, ContentExtractor contentExtractor, boolean checkValueExists) {
+            this.paramName = paramName;
+            this.contentExtractor = contentExtractor;
+            this.checkValueExists = checkValueExists;
+        }
+    }
+
+    public static class SetSessionParamOperation extends SetScopeParamOperation {
+
+        protected final boolean autocreate;
+
+        public SetSessionParamOperation(String paramName, ContentExtractor contentExtractor, boolean checkValueExists) {
+            this(paramName, contentExtractor, checkValueExists, true);
+        }
+
+        public SetSessionParamOperation(String paramName, ContentExtractor contentExtractor, boolean checkValueExists, boolean autocreate) {
+            super(paramName, contentExtractor, checkValueExists);
+            this.autocreate = autocreate;
+        }
+
+        @Override
+        public void process(HttpResponse response, HttpFlowContext context) {
+            ParametersBag params = context.scopes().getSession(response, autocreate);
+            if (params != null) {
+                if (checkValueExists && params.contains(paramName)) {
+                    return;
+                }
+
+                try {
+                    String value = contentExtractor.extract(response);
+                    params.set(paramName, value);
+                } catch (IOException ex) {
+                    LOGGER.info("Could not read message body", ex);
+                }
+            }
+        }
+    }
+
+    public static class SetGlobalParamOperation extends SetScopeParamOperation {
+
+        public SetGlobalParamOperation(String paramName, ContentExtractor contentExtractor, boolean checkValueExists) {
+            super(paramName, contentExtractor, checkValueExists);
+        }
+
+        @Override
+        public void process(HttpResponse response, HttpFlowContext context) {
+            ParametersBag params = context.scopes().getGlobal();
+            if (checkValueExists && params.contains(paramName)) {
+                return;
+            }
+
+            try {
+                String value = contentExtractor.extract(response);
+                params.set(paramName, value);
+            } catch (IOException ex) {
+                LOGGER.info("Could not read message body", ex);
+            }
+        }
+    }
+
+    private static final Logger LOGGER = LogManager.getLogger(HttpResponseContentExtractorFilter.class);
+
+    private HttpMessagePredicate predicate;
+    private List<Operation> operations = new ArrayList<>();
+
+    public void addOperation(Operation operation) {
+        Assert.notNull(operation, "operation");
+        operations.add(operation);
+    }
+
+    HttpMessagePredicate getPredicate() {
+        return predicate;
+    }
+
+    List<Operation> getOperations() {
+        return operations;
+    }
+
+    @Override
+    public void configure(Configuration config) {
+        List<Operation> ops = (List<Operation>) config.get("operations");
+        operations.clear();
+
+        if (ops != null) {
+            for (Operation op : ops) {
+                addOperation(op);
+            }
+        }
+
+        predicate = (HttpMessagePredicate) config.get("applyIf", null);
+    }
+
+    @Override
+    public int filterInbound(HttpRequest request, HttpResponse resp, HttpFlowContext context) {
+        if (resp != null && !operations.isEmpty()) {
+            boolean exec = true;
+            if (predicate != null) {
+                HttpMessageWrapper wrapper = new HttpMessageWrapper(request, resp, context);
+                exec = predicate.test(wrapper);
+            }
+
+            if (exec) {
+                for (Operation op : operations) {
+                    op.process(resp, context);
+                }
+            }
+        }
+        return DUNNO;
+    }
+
+    @Override
+    public HttpResponseContentExtractorFilter instanceForWorker(int index) {
+        HttpResponseContentExtractorFilter filter = new HttpResponseContentExtractorFilter();
+        filter.predicate = predicate;
+        filter.operations.addAll(operations);
+        return filter;
+    }
+
+    public static class NodeDefCreator implements NodeDefinitionCreator {
+
+        @Override
+        public NodeDefinition create() {
+            // TODO: jak pozwolić na dokładnie jedno z poniższych
+            MapNodeDefinition mapDef = mapDef(
+                    tupleDef("$regex", valueDef()).setRequired(false),
+                    tupleDef("$xpath", valueDef()).setRequired(false)
+            );
+
+            HeaderOperationNodeDefinition setSessionDef = new HeaderOperationNodeDefinition(mapDef);
+            setSessionDef.addParam(tupleDef("@autocreate", valueDefBool()).setRequired(false));
+
+            HeaderOperationNodeDefinition setGlobalDef = new HeaderOperationNodeDefinition(mapDef);
+
+            KeyNameVaryListNodeDefinition operationsDef = new KeyNameVaryListNodeDefinition()
+                    .setNodeTransformer(new HttpResponseContentExtractorFilterTransformer())
+                    .add("$addSessionParam", setSessionDef)
+                    .add("$setSessionParam", setSessionDef)
+                    .add("$addGlobalParam", setGlobalDef)
+                    .add("$setGlobalParam", setGlobalDef);
+
+            return mapDef(
+                    tupleDef("operations", operationsDef),
+                    tupleDef("applyIf", new HttpFilterMessagePredicateNodeDefinition()).setRequired(false)
+            );
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpResponseContentExtractorFilterTransformer.java	Fri Aug 11 10:32:25 2017 +0200
@@ -0,0 +1,145 @@
+package com.passus.st.client.http.filter;
+
+import com.passus.config.CMapNode;
+import com.passus.config.CNode;
+import com.passus.config.CTupleNode;
+import com.passus.config.CValueNode;
+import static com.passus.config.ConfigurationUtils.extractBoolean;
+import com.passus.config.NodeType;
+import com.passus.config.schema.NodeTransformer;
+import com.passus.config.validation.Errors;
+import com.passus.st.client.http.extractor.ContentExtractor;
+import com.passus.st.client.http.extractor.ContentExtractorTransformer;
+import com.passus.st.client.http.filter.HttpResponseContentExtractorFilter.Operation;
+import com.passus.st.client.http.filter.HttpResponseContentExtractorFilter.SetGlobalParamOperation;
+import com.passus.st.client.http.filter.HttpResponseContentExtractorFilter.SetSessionParamOperation;
+import static com.passus.st.validation.NodeValidationUtils.validateType;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ *
+ * @author mikolaj.podbielski
+ */
+public class HttpResponseContentExtractorFilterTransformer implements NodeTransformer<CValueNode> {
+
+    private final ContentExtractorTransformer extractorTransformer = ContentExtractorTransformer.DEFAULT;
+
+    private SetSessionParamOperation createSetSessionParamOperation(CTupleNode tuple, Errors errors, boolean checkValueExists) {
+        if (validateType(tuple.getNode(), NodeType.MAP, errors)) {
+            CMapNode mapNode = (CMapNode) tuple.getNode();
+
+            boolean autocreate = false;
+            String paramName = null;
+            ContentExtractor extractor = null;
+            for (CTupleNode mapTupleNode : mapNode.getChildren()) {
+                try {
+                    errors.pushNestedPath(mapTupleNode.getName());
+                    switch (mapTupleNode.getName()) {
+                        case "@autocreate":
+                            autocreate = extractBoolean(mapTupleNode, errors);
+                            break;
+                        default:
+                            try {
+                                paramName = mapTupleNode.getName();
+                                extractor = extractorTransformer.transform(mapTupleNode.getNode());
+                            } catch (Exception e) {
+                                errors.reject(mapTupleNode.getNode(), "Invalid expression.");
+                            }
+
+                    }
+                } finally {
+                    errors.popNestedPath();
+                }
+            }
+
+            return new SetSessionParamOperation(paramName, extractor, checkValueExists, autocreate);
+        }
+
+        return null;
+    }
+
+    private SetGlobalParamOperation createSetGlobalParamOperation(CTupleNode tuple, Errors errors, boolean checkValueExists) {
+        if (validateType(tuple.getNode(), NodeType.MAP, errors)) {
+            CMapNode mapNode = (CMapNode) tuple.getNode();
+
+            String paramName = null;
+            ContentExtractor extractor = null;
+            for (CTupleNode mapTupleNode : mapNode.getChildren()) {
+                try {
+                    errors.pushNestedPath(mapTupleNode.getName());
+                    switch (mapTupleNode.getName()) {
+                        default:
+                            try {
+                                paramName = mapTupleNode.getName();
+                                extractor = extractorTransformer.transform(mapTupleNode.getNode());
+                            } catch (Exception e) {
+                                errors.reject(mapTupleNode.getNode(), "Invalid expression.");
+                            }
+
+                    }
+                } finally {
+                    errors.popNestedPath();
+                }
+            }
+
+            return new SetGlobalParamOperation(paramName, extractor, checkValueExists);
+        }
+
+        return null;
+    }
+
+    @Override
+    public CValueNode transform(CNode node, Errors errors) {
+        CMapNode mapNode = (CMapNode) node;
+
+        List<CTupleNode> tuples = mapNode.getChildren();
+        List<Operation> operations;
+        if (tuples.isEmpty()) {
+            operations = Collections.EMPTY_LIST;
+        } else {
+            operations = new ArrayList<>();
+        }
+
+        for (CTupleNode tuple : tuples) {
+            String opName = tuple.getName();
+
+            try {
+                errors.pushNestedPath(opName);
+                Operation op = null;
+                switch (opName.toLowerCase()) {
+                    case "$addsessionparam":
+                        op = createSetSessionParamOperation(tuple, errors, true);
+                        break;
+                    case "$setsessionparam":
+                        op = createSetSessionParamOperation(tuple, errors, false);
+                        break;
+                    case "$addglobalparam":
+                        op = createSetGlobalParamOperation(tuple, errors, true);
+                        break;
+                    case "$setglobalparam":
+                        op = createSetGlobalParamOperation(tuple, errors, false);
+                        break;
+                    default:
+                        throw new IllegalStateException("Not supported operation '" + opName + "'.");
+
+                }
+
+                if (op != null) {
+                    operations.add(op);
+                }
+            } finally {
+                errors.popNestedPath();
+            }
+        }
+
+        return new CValueNode(operations);
+    }
+
+    @Override
+    public CValueNode reverseTransform(CNode node, Errors errors) {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+}
--- a/stress-tester/src/test/java/com/passus/st/client/http/filter/HttpFilterTestUtils.java	Thu Aug 10 14:03:21 2017 +0200
+++ b/stress-tester/src/test/java/com/passus/st/client/http/filter/HttpFilterTestUtils.java	Fri Aug 11 10:32:25 2017 +0200
@@ -1,9 +1,12 @@
 package com.passus.st.client.http.filter;
 
+import com.passus.config.validation.Errors;
+import com.passus.config.validation.ObjectError;
 import com.passus.net.http.HttpMessage;
 import static com.passus.st.client.http.HttpConsts.TAG_SESSION_ID;
 import com.passus.st.client.http.HttpFlowContext;
 import com.passus.st.client.http.HttpScopes;
+import java.util.List;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -26,4 +29,13 @@
         }
     }
 
+    protected static void printErrors(Errors errors) {
+        List<ObjectError> allErrors = errors.getAllErrors();
+        if (allErrors.size() > 0) {
+            System.out.println("ERRORS:");
+            for (ObjectError error : allErrors) {
+                System.out.println(error);
+            }
+        }
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stress-tester/src/test/java/com/passus/st/client/http/filter/HttpResponseContentExtractorFilterTest.java	Fri Aug 11 10:32:25 2017 +0200
@@ -0,0 +1,126 @@
+package com.passus.st.client.http.filter;
+
+import com.passus.config.validation.Errors;
+import com.passus.net.http.HttpResponse;
+import com.passus.net.http.HttpResponseBuilder;
+import com.passus.st.client.http.HttpConsts;
+import com.passus.st.client.http.HttpFlowContext;
+import com.passus.st.client.http.HttpScopes;
+import com.passus.st.client.http.extractor.ContentExtractor;
+import com.passus.st.client.http.extractor.RegexValueExtractor;
+import com.passus.st.client.http.extractor.XmlValueExtractor;
+import static com.passus.st.client.http.filter.HttpFilterTestUtils.createMockContext;
+import com.passus.st.client.http.filter.HttpResponseContentExtractorFilter.Operation;
+import com.passus.st.client.http.filter.HttpResponseContentExtractorFilter.SetGlobalParamOperation;
+import com.passus.st.client.http.filter.HttpResponseContentExtractorFilter.SetSessionParamOperation;
+import java.util.List;
+import static org.testng.AssertJUnit.*;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author mikolaj.podbielski
+ */
+public class HttpResponseContentExtractorFilterTest {
+
+    private final String sessionId = "sessionId";
+    private final ConstContentExtractor oldValue = new ConstContentExtractor("oldValue");
+    private final ConstContentExtractor newValue = new ConstContentExtractor("newValue");
+
+    private HttpResponse createResponse() {
+        return HttpResponseBuilder.ok()
+                .tag(HttpConsts.TAG_SESSION_ID, sessionId)
+                .content("content")
+                .build();
+    }
+
+    @Test
+    public void testSetSessionParamOperation() {
+        HttpFlowContext mockContext = createMockContext();
+        HttpScopes scopes = mockContext.scopes();
+        HttpResponse response = createResponse();
+
+        HttpResponseContentExtractorFilter filter = new HttpResponseContentExtractorFilter();
+        filter.addOperation(new SetSessionParamOperation("paramName", oldValue, true));
+        filter.filterInbound(null, response, mockContext);
+        assertEquals("oldValue", scopes.getSession(sessionId, false).get("paramName"));
+
+        filter = new HttpResponseContentExtractorFilter();
+        filter.addOperation(new SetSessionParamOperation("paramName", newValue, true));
+        filter.filterInbound(null, response, mockContext);
+        assertEquals("oldValue", scopes.getSession(sessionId, false).get("paramName"));
+    }
+
+    @Test
+    public void testSetGlobalParamOperation() {
+        HttpFlowContext mockContext = createMockContext();
+        HttpScopes scopes = mockContext.scopes();
+        HttpResponse response = createResponse();
+
+        HttpResponseContentExtractorFilter filter = new HttpResponseContentExtractorFilter();
+        filter.addOperation(new SetGlobalParamOperation("paramName", oldValue, true));
+        filter.filterInbound(null, response, mockContext);
+        assertEquals("oldValue", scopes.getGlobal().get("paramName"));
+
+        filter = new HttpResponseContentExtractorFilter();
+        filter.addOperation(new SetGlobalParamOperation("paramName", newValue, true));
+        filter.filterInbound(null, response, mockContext);
+        assertEquals("oldValue", scopes.getGlobal().get("paramName"));
+    }
+
+    @Test
+    public void testConfigure() throws Exception {
+        String filterConfig = "filters:\n"
+                + "  - type: responseContentExtractor\n"
+                + "    applyIf:\n"
+                + "      \"req.uri\": {$contains: \"/pydio/\"}\n"
+                + "    operations:\n"
+                + "      $setSessionParam:\n"
+                + "        \"@autocreate\": true\n"
+                + "        SecureToken: {$regex: '\"SECURE_TOKEN\"[ ]*:[ ]*\"(.*?)\"'}\n"
+                + "      $addGlobalParam:\n"
+                + "        SomeVariable: {$xpath: 'root.abc'}";
+
+        Errors errors = new Errors();
+        List<HttpFilter> filters = HttpFiltersConfigurator.getFilters(filterConfig, errors);
+        HttpFilterTestUtils.printErrors(errors);
+
+        assertEquals(0, errors.getErrorCount());
+        assertEquals(1, filters.size());
+        assertTrue(filters.get(0) instanceof HttpResponseContentExtractorFilter);
+
+        HttpResponseContentExtractorFilter filter = (HttpResponseContentExtractorFilter) filters.get(0);
+        
+        assertNotNull(filter.getPredicate());
+        
+        List<Operation> operations = filter.getOperations();
+        assertEquals(2, operations.size());
+        assertTrue(operations.get(0) instanceof SetSessionParamOperation);
+        assertTrue(operations.get(1) instanceof SetGlobalParamOperation);
+
+        SetSessionParamOperation op0 = (SetSessionParamOperation) operations.get(0);
+        assertEquals(true, op0.autocreate);
+        assertEquals(false, op0.checkValueExists);
+        assertEquals("SecureToken", op0.paramName);
+        assertTrue(op0.contentExtractor instanceof RegexValueExtractor);
+        
+        SetGlobalParamOperation op1 = (SetGlobalParamOperation) operations.get(1);
+        assertEquals(true, op1.checkValueExists);
+        assertEquals("SomeVariable", op1.paramName);
+        assertTrue(op1.contentExtractor instanceof XmlValueExtractor);
+    }
+
+    private static class ConstContentExtractor implements ContentExtractor {
+
+        private final String value;
+
+        public ConstContentExtractor(String value) {
+            this.value = value;
+        }
+
+        @Override
+        public String extract(String content) {
+            return value;
+        }
+    }
+}
--- a/stress-tester/src/test/java/com/passus/st/client/http/filter/HttpScopeModificationFilterTest.java	Thu Aug 10 14:03:21 2017 +0200
+++ b/stress-tester/src/test/java/com/passus/st/client/http/filter/HttpScopeModificationFilterTest.java	Fri Aug 11 10:32:25 2017 +0200
@@ -28,6 +28,8 @@
 public class HttpScopeModificationFilterTest {
 
     private final String sessionId = "sessionId";
+    private final UnmutableValueExtractor oldValue = new UnmutableValueExtractor("paramValue");
+    private final UnmutableValueExtractor newValue = new UnmutableValueExtractor("newParamValue");
 
     @BeforeClass
     public static void beforeClass() {
@@ -82,12 +84,12 @@
         HttpRequest req = createRequest();
 
         HttpScopeModificationFilter filter = new HttpScopeModificationFilter();
-        filter.addOperation(new SetSessionParamOperation("paramName", new UnmutableValueExtractor("paramValue"), true));
+        filter.addOperation(new SetSessionParamOperation("paramName", oldValue, true));
         filter.filterOutbound(req, null, mockContext);
         assertEquals("paramValue", scopes.getSession(sessionId, false).get("paramName"));
 
-        filter.clearOperations();
-        filter.addOperation(new SetSessionParamOperation("paramName", new UnmutableValueExtractor("newParamValue"), true));
+        filter = new HttpScopeModificationFilter();
+        filter.addOperation(new SetSessionParamOperation("paramName", newValue, true));
         filter.filterOutbound(req, null, mockContext);
         assertEquals("paramValue", scopes.getSession(sessionId, false).get("paramName"));
     }
@@ -99,12 +101,12 @@
         HttpRequest req = createRequest();
 
         HttpScopeModificationFilter filter = new HttpScopeModificationFilter();
-        filter.addOperation(new SetSessionParamOperation("paramName", new UnmutableValueExtractor("paramValue"), false));
+        filter.addOperation(new SetSessionParamOperation("paramName", oldValue, false));
         filter.filterOutbound(req, null, mockContext);
         assertEquals("paramValue", scopes.getSession(sessionId, false).get("paramName"));
 
-        filter.clearOperations();
-        filter.addOperation(new SetSessionParamOperation("paramName", new UnmutableValueExtractor("newParamValue"), false));
+        filter = new HttpScopeModificationFilter();
+        filter.addOperation(new SetSessionParamOperation("paramName", newValue, false));
         filter.filterOutbound(req, null, mockContext);
         assertEquals("newParamValue", scopes.getSession(sessionId, false).get("paramName"));
     }
@@ -131,12 +133,12 @@
         HttpRequest req = createRequest();
 
         HttpScopeModificationFilter filter = new HttpScopeModificationFilter();
-        filter.addOperation(new SetGlobalParamOperation("paramName", new UnmutableValueExtractor("paramValue"), true));
+        filter.addOperation(new SetGlobalParamOperation("paramName", oldValue, true));
         filter.filterOutbound(req, null, mockContext);
         assertEquals("paramValue", scopes.getGlobal().get("paramName"));
 
-        filter.clearOperations();
-        filter.addOperation(new SetSessionParamOperation("paramName", new UnmutableValueExtractor("newParamValue"), true));
+        filter = new HttpScopeModificationFilter();
+        filter.addOperation(new SetSessionParamOperation("paramName", newValue, true));
         filter.filterOutbound(req, null, mockContext);
         assertEquals("paramValue", scopes.getGlobal().get("paramName"));
     }
@@ -148,12 +150,12 @@
         HttpRequest req = createRequest();
 
         HttpScopeModificationFilter filter = new HttpScopeModificationFilter();
-        filter.addOperation(new SetGlobalParamOperation("paramName", new UnmutableValueExtractor("paramValue"), false));
+        filter.addOperation(new SetGlobalParamOperation("paramName", oldValue, false));
         filter.filterOutbound(req, null, mockContext);
         assertEquals("paramValue", scopes.getGlobal().get("paramName"));
 
-        filter.clearOperations();
-        filter.addOperation(new SetGlobalParamOperation("paramName", new UnmutableValueExtractor("newParamValue"), false));
+        filter = new HttpScopeModificationFilter();
+        filter.addOperation(new SetGlobalParamOperation("paramName", newValue, false));
         filter.filterOutbound(req, null, mockContext);
         assertEquals("newParamValue", scopes.getGlobal().get("paramName"));
     }