Skip to content
/ forgery Public

An auxiliary tool used to mimick requests to %CSP.REST and Frontier.Router based classes

License

Notifications You must be signed in to change notification settings

rfns/forgery

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Forgery

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.

How it works

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.

Forgery request flow

Agent

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.

Usage

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

Default settings

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)

Response and CSP 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.

About the Jar

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).

Troubleshooting

I provided a URL but it's not finding my dispatch class?

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:

  1. 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.

  2. 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.

What if my application uses a token-based approach?

You can simulate authenticated request by providing an Authorization header inside the headers object.

I want to try sending a file, but I have no idea from where to begin!

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.

Known issues

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.

CONTRIBUTING

If you want to contribute with this project. Please read the CONTRIBUTING file.

LICENSE

MIT.