Introducing scripted testing for Fastly fiddle
Fastly Fiddle allows for instant experimentation with Fastly's edge cloud behaviours without having to set up a Fastly account. Now, we're adding the ability to define assertions to specify the behaviour you are trying to create.
With the recent launch of our library of solution recipes that make it easier to build on Fastly, we now can demonstrate a selection of the solutions that people have created to run on Fastly. Our growing library of recipes is nearing a hundred, and in building these, we realised that we needed a better way not only to verify that the solutions continue to work as we evolve our network, but also to make it clear what the solution is achieving.
Test for fiddles
Fiddle presents a lot of information— and it can be overwhelming. How do you know what the key point is that the solution has actually achieved? We already automatically highlight 'interesting' HTTP headers, based on an analysis of your VCL code, but now, you can explicitly define what your fiddle is supposed to do:
To define tests, we start by writing a fiddle containing the logic that we want to test. Taking a simple example, we could add a header to each request as it comes into Fastly, so that the information in that new header is transmitted to your origin.
Here's a fiddle that does that. Feel free to run it:
Testing this behaviour in a normal Fastly service by external observation would be hard. There's no evidence in the response back to the browser that your header got added to the request to the origin. What we need to do is be able to assert things about the internal behaviour of Fastly.
Test targets: what we can test
There are three major sources of data about requests transiting Fastly: the client-side request/response, the origin request/response, and the events that are triggered within the Fastly platform. We expose these in our test syntax as clientFetch
, originFetches
(there may be more than one), and events
.
These three top level objects expose lots of interesting and useful properties:
clientFetch | Obj | Petición procedente del cliente y dirigida a Fastly \(y la respuesta de Fastly\). |
.req | Str | Bloque de peticiones HTTP. Contiene el método de petición, la ruta, la versión HTTP, los pares clave/valor del encabezado y el cuerpo de la petición. |
.resp | Str | Encabezado de respuesta HTTP. Contiene la línea de estado de respuesta y los encabezados de respuesta \(no el cuerpo\). |
.respType | Str | Valor de encabezado de respuesta Content\-type analizado \(solo tipo mime\). |
.isText | Bool | Si el cuerpo de la respuesta puede o no tratarse como texto. |
.isImage | Bool | Si el cuerpo de la respuesta puede tratarse o no como imagen. |
.status | Num | Estado de la respuesta HTTP. |
.bodyPreview | Str | Vista previa de texto UTF\-8 del cuerpo \(truncado en 1K\). |
.bodyBytesReceived | Num | Cantidad de datos recibidos. |
.bodyChunkCount | Num | Número de fragmentos recibidos. |
.complete | Bool | Si la respuesta está completa o no. |
.trailers | Str | Colas de respuesta HTTP. |
originFetches | Array | Recuperaciones de origen realizadas durante la petición. |
\[idx\] | Obj | Cada recuperación es un objeto. |
.vclFlowKey | Str | ID del flujo de VCL que desencadenó esta recuperación. |
.req | Str | Bloque de peticiones HTTP. Contiene el método de petición, la ruta, la versión HTTP, los pares clave/valor del encabezado y el cuerpo de la petición. |
.resp | Str | Encabezado de respuesta HTTP. Contiene la línea de estado de respuesta y los encabezados de respuesta \(no el cuerpo\). |
.remoteAddr | Str | Dirección IP de origen resuelta. |
.remotePort | Num | Puerto en el servidor de origen. |
.remoteHost | Str | Nombre del host del servidor de origen. |
.elapsedTime | Num | Tiempo total dedicado a la recuperación de origen \(ms\). |
events | Array | Eventos VCL de Fastly relacionados con la petición, en el orden de flujo VCL. Puede contener varios flujos VCL si hay reinicios, ESI o protección. |
\[idx\] | Obj | Cada elemento de matriz es un evento VCL. |
.fnName | Str | Tipo de evento. Puede ser «recv» «hash», «hit», «miss», «pass», «waf», «fetch», «deliver», «error» o «log». |
.datacenter | Str | Código de tres letras que identifica la ubicación del centro de datos de Fastly en el que ocurrió este evento; por ejemplo, «LCY», «JFK» o «SYD». |
.nodeID | Str | Identificador numérico del servidor concreto en el que ocurrió este evento. |
.originFetch | Obj | Recuperación del origen asociada al evento \(consulta el modelo en originFetches, dispuesto anteriormente\). Solo los eventos FETCH tienen una propiedad originFetch. |
.logs | Array | Matriz de cadenas y mensajes registrados de este evento. |
. | Cualquier propiedad que se notifique en la IU del fiddle respecto de un evento se puede señalar para someterla a pruebas. Por ejemplo, dentro de los eventos MISS, se notifica una propiedad «staleExists». | |
logs | Array | Matriz de cadenas, y mensajes registrados de *todos* los eventos VCL. |
insights | Array | Etiquetas de información pertinente relativas a esta petición. Las etiquetas de información pertinente identifican recomendaciones o discrepancias con respecto a prácticas recomendadas. Por ejemplo: \[ "client\-cc\-missing", "invalid\-header" \]. |
To get to the headers that have been added to the origin request, we need to access the req property of the first item in the originRequests
collection:
Simple enough, but what if you wanted to also check that the response from the origin was cached? We would need to access the .ttl
property of the fetch event, but we can't be sure what the index of the fetch event is within the events collection. What is needed is a way to filter the events collection for just the fetch events, and then pick the first one.
Aggregation functions
To make accessing test targets easier, we've added a set of aggregation functions. Drop these into the target string to transform the data:
listBy\(field\) | Array => Array | Toma una matriz de objetos y hace una matriz de matrices, donde en cada submatriz todos los objetos comparten el mismo valor de «field». Los elementos de la matriz de salida se ordenan en función del orden en el que aparecen en la entrada los valores del campo seleccionado. `events.listBy(vclflowkey)[1][0].fnName is "recv"` |
where\(field=val\) | Array => Array | Toma una matriz de objetos y la filtra de modo que deja solo aquellos donde la propiedad llamada «field» tiene el valor «val». `events.where(fnName=recv)[0].url startsWith "/a/"` |
groupBy\(field\) | Array => Obj | Toma una matriz de objetos y la divide en múltiples matrices, cada una de las cuales tiene el mismo valor en la propiedad «field», y organiza los datos resultantes en un objeto cuyos valores de «field» son claves. `events.groupBy(fnName).recv[0].url startsWith "/a/"` |
transpose\(\) | Array => Obj | Toma una matriz de objetos y hace un objeto de matrices. Cuando varios objetos de entrada comparten el mismo nombre de propiedad, esa propiedad se convierte en una propiedad de nivel superior que tiene una matriz que contiene todos los valores. `events.transpose().return notIncludes "error"` |
count\(\) | Array => Num | Toma una matriz y devuelve la longitud. `originFetches.count() greaterThan 1` |
concat\(\) | Array => Str | Toma una matriz y devuelve una representación de cadena de todos los elementos de la matriz unidos entre sí y delimitados por nuevas líneas. `logs.concat() includes "Hello"` |
So, now we can identify the first fetch event's ttl property with:
Finally, you need to assert something about this target data.
Assertions
We support any assertion defined in Chai, and have added a few more. These are likely to be the ones that are most useful to you:
So, now we can form the full tests for our example fiddle:
Note that the ttl property, along with all timing properties reported by Fastly, is in microseconds - so 3600 seconds (1 hour) is 3,600,000,000 μs. Let's look at how these tests are added to a fiddle through the UI:
Try running the fiddle again, but this time watch as the tests are run!
Tests are parsed when your fiddle runs, and will tell you if any of your test syntax is unparseable.
Debugging
This is all well and good if your tests are passing, but what if they are failing? How do you know that you wrote the test correctly, vs the fiddle itself having a bug that the test has caught?
If a test fails when it is run, Fiddle will display not just the failure state but also the actual value of the target:
This can also be a great help when constructing the target string. If you're not sure what properties exist on a target path, you can just write a test using that path, allowing it to fail so you can see what the target actually looks like:
Tests will run asynchronously and need to wait for asynchronous instrumentation to be delivered. For some targets, the delivery of the instrumentation data takes longer than others. This is indicated by the hourglass icon on each test — no hourglass, no delay; running hourglass, short delay; completed hourglass, longer delay.
We hope that being able to run tests in fiddle helps to validate Fastly configurations and provide additional confidence in moving your logic to the edge. If you have any ideas or suggestions, feel free to post below in the comments or drop me a line.