Forgery is a server-side utility that allows executing simulated HTTP request by forging calls to REST applications. This makes Forgery ideal for using together with test suites that need to call the API via HTTP but could face issues with license usage and its grace period.
NOTE: This is not a tool used to bypass license limits, but instead it's simply an auxiliary tool for facilitating authoring request tests and debugging them without hitting the network layer. Because let's face it, debugging requests is actually a pain.
After retrieving the web application and dispatch class from the provided url. A device redirection is put in play to capture all the outputs coming from the dispatch class. The diagram below describes how the mimicked request is transferred to the actual dispatcher.
The Agent is responsible for mimicking a client that executes a request to the server. It's represented by the class Forgery.Agent
which is composed by six shortcut methods:
- Post
- Put
- Delete
- Patch
- Options
- Head
Each method accepts a configuration that might looks similar to the Fetch API.
Calling a resource that replies to POST requests.
set agent = ##class(Forgery.Agent).%New()
set sc = agent.Post({
"url": "api/forgery/samples/echo",
"headers": {
"Content-Type": "application/json; charset=utf-8"
},
"data": {
"message": "Send me back"
}
},
.response,
1, // Automatically outputs the response to the current device.
)
if $$$ISERR(sc) write "Received a server error: "_$System.Status.GetErrorText(sc)
return $$$OK
You can also provide an object with the following default settings:
baseURL
: A prefix for a URL path.defaultHeaders
: An object hash of header name and values.
set agent = ##class(Forgery.Agent).%New({
"baseURL": "/my/web/app/name",
"headers": {
"Content-Type": "application/json; charset=utf-8"
}
})
// Any requests that begins with `agent` will be using the default configuration.
set sc = agent.Get("/", .response)
You can also retrieve the last response and %CSP.Response instance:
// This returns the last reply sent by the "server".
set response = agent.GetLastResponse()
// And this is how the %response object has been filled.
set %response = agent.GetLastCSPResponse()
Retrieving the %CSP.Response object allows you to execute fine-grained assertions.
The cookie Jar is a memory store managed by the agent. It uses the jar to store any cookies from the CSP response object. This allows the agent to re-use these cookies without the developer having to hard code it every request. In addition, the jar is protected from external tampering in order to provide a safe environment to simulate httpOnly cookies.
Although the jar stores cookies from multiple requests, the agent will pick only the ones that are relevant to the URL and each cookie's Path attribute (if present).
Since you're not actually hitting your web server, you only need to provide the path that ressembles your web application's name/path onwards. Do not provide the hostname, protocol or any path that comes before the web application name.
My application uses custom cookies and I tried setting them using the Set-Cookies header but to no avail?
There are two ways of doing this:
-
Since multiple cookies with the same name can be set, I decided to dedicate a setting for it: by using the
cookies
setting, which receives a key-value object or an array of values for each named key. -
By reusing the agent for subsequent request, this way the previous request will have set the cookie, which is good to simulate working with httpOnly cookies.
If you need to set cookies don't use the Set-Cookies
, because the dispatch class won't know from where to read it.
You can simulate authenticated request by providing an Authorization
header inside the headers
object.
You can simulate FormData requests by using the mimedata
setting. Just pass out a file stream to a key-valued object and you'll be done. e.g.:
{
"mimedata": {
"my_file_key": (myFileStream)
}
}
If you need to repeat the name, you can provide a %DynamicArray
of streams for that name instead.
The following situations are known issues. If you have any ideas on how to fix it, please let me know:
-
Methods that call APIs to generate files (like handling uploads) will mostly like fail, this is due to the redirection required to capture the content being written, which in turn conflicts with the device change required to write files. The current workaround is to ignore the file generation and check if the request handler method completed without issues.
-
Some dispatch classes like the one that exposes the Atelier API will result into an odd response: they'll mix the success and error objects. I'm still trying to figure what is causing that.
If you want to contribute with this project. Please read the CONTRIBUTING file.
MIT.