Skip to content

Commit

Permalink
feat: Improvements (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
mamari90 authored Feb 12, 2024
1 parent 7d3f064 commit 851834e
Show file tree
Hide file tree
Showing 8 changed files with 382 additions and 41 deletions.
59 changes: 37 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,40 @@ Azure function to monitor internal and external service status, reporting to App
This application relies on a configuration structure stored on the configured table storage structured as follows:


| Column Name | description | required |
|------------------|------------------------------------------------------------------------------------------------------------------------------------------------|----------|
| PartitionKey | string. name of the app being monitored | yes |
| RowKey | string. name of the api being monitored | yes |
| url | string. url to be monitored | yes |
| method | string, upper case. method to be used when invoking `url` | yes |
| body | json stringifyied. body of the to be provided during the request. Allowed only when `method` is `PATCH`, `POST`, `DELETE`, `PUT` | no |
| expectedCodes | json stringifyied string. list of string and ranges (eg "200-220") of accepted http status (considered a success for the availability metric) | yes |
| type | string. identified of the api type being called. suggested types: `private`, `public` | yes |
| checkCertificate | boolean. if true, also checks the server's certificate (expiration, version) | yes |
| durationLimit | number. threshold, in milliseconds, over which a response time will trigger a failed availability test. to not be confused with `HTTP_CLIENT_TIMEOUT` env variable | yes |
| tags | json stringifyied. dictionary of tags to be added to the tracked metrics | yes |
| headers | json stringifyied. dictionary of headers to be sent in the http request | no |
| Column Name | description | required |
|---------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|
| PartitionKey | string. name of the app being monitored | yes |
| RowKey | string. name of the api being monitored | yes |
| url | string. url to be monitored | yes |
| method | string, upper case. method to be used when invoking `url` | yes |
| body | json stringifyied. body of the to be provided during the request. Allowed only when `method` is `PATCH`, `POST`, `DELETE`, `PUT` | no |
| expectedCodes | json stringifyied string. list of string and ranges (eg "200-220") of accepted http status (considered a success for the availability metric) | yes |
| type | string. identified of the api type being called. suggested types: `private`, `public` | yes |
| checkCertificate | boolean. if true, also checks the server's certificate (expiration, version) | yes |
| durationLimit | number. threshold, in milliseconds, over which a response time will trigger a failed availability test. to not be confused with `HTTP_CLIENT_TIMEOUT` env variable | yes |
| tags | json stringifyied. dictionary of tags to be added to the tracked metrics | yes |
| headers | json stringifyied. dictionary of headers to be sent in the http request | no |
| bodyCompareStrategy | strategy to be used when compating received response body to `expectedBoddy`. Possible values: `contains`, `containsKeys`, `listOfTypes`, `typeOf` | no |
| expectedBody | json stringifyied. expected body type/content. used in conjunction with `bodyCompareStrategy` | no |

Note on the `type`: any value is accepted, it will be traced in the application insight availability panel as "runLocation".
suggested values are:
- `private`: means that the api being tested is reached through internal network (vnet)
- `public`: means that the api being tested is reached through internet


**Detail on compareStrategy**

| Method | Functionality | Applied to |
|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------|
| contains | checks if all the fields defined in the expected body are present in the actual body, and if their value are equal (recursively). On object arrays, checks if the received array has exactly the same element of the expected, using this `contains` on each element for comparison. On primitive arrays, checks if all the expected elements are included n the received, using `Array.includes()` for comparison | objects |
| containsKeys | checks if all the fields defined in the expected body are present in the actual body, and if their value are of the expected type (recursively). Values associable to object keys: `bool` `string` `number` `array`, `object`. You can also define the array content type, using `["number"]` | objects |
| listOfTypes | checks if the response is a list containing the types defined in the `body` field. Uses the `containsKeys` logic to check each element of the list | array |
| typeOf | checks if the response type is as expected. supports all types returned by javascript `typeof` | any |




here's an example in json format to better understand the content
```json
{
Expand Down Expand Up @@ -62,15 +76,16 @@ When checking the certificate, the suffix `-cert` will be appended to the "runLo

## Env variables

| name | description | required | default |
|-------------------------------|------------------------------------------------------------------------------------------------|----------|-----------|
| APP_INSIGHT_CONNECTION_STRING | application insight connection string. where to publish availability metrics and custom events | yes | - |
| STORAGE_ACCOUNT_NAME | storage account name used to store the monitoring configuration | yes | - |
| STORAGE_ACCOUNT_KEY | storage account access key | yes | - |
| STORAGE_ACCOUNT_TABLE_NAME | table name used to store the monitoring configuration | yes | - |
| AVAILABILITY_PREFIX | prefix used in the custom metric and events names | no | synthetic |
| HTTP_CLIENT_TIMEOUT | timeout used by the http client performing the availability requests | yes | - |
| LOCATION | region name where this job is run | yes | - |
| name | description | required | default |
|-------------------------------|-------------------------------------------------------------------------------------------------------|----------|-----------|
| APP_INSIGHT_CONNECTION_STRING | application insight connection string. where to publish availability metrics and custom events | yes | - |
| STORAGE_ACCOUNT_NAME | storage account name used to store the monitoring configuration | yes | - |
| STORAGE_ACCOUNT_KEY | storage account access key | yes | - |
| STORAGE_ACCOUNT_TABLE_NAME | table name used to store the monitoring configuration | yes | - |
| AVAILABILITY_PREFIX | prefix used in the custom metric and events names | no | synthetic |
| HTTP_CLIENT_TIMEOUT | timeout used by the http client performing the availability requests | yes | - |
| LOCATION | region name where this job is run | yes | - |
| CERT_VALIDITY_RANGE_DAYS | number of days before the expiration date of a certificate over which the check is considered success | yes | - |

## Deploy

Expand Down
123 changes: 123 additions & 0 deletions src/comparator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
function compare(strategyName, actual, expected){
if(strategyName in strategies){
return strategies[strategyName](actual, expected)
}else {
// default value if strategy not found
return false
}
}

const contains = function (actual, expected) {
let result = true;
try {
Object.keys(expected).forEach((key) => {
console.debug(`checking object key: ${key}`)
if (typeof expected[key] === 'object') {
if (expected[key] instanceof Array) {//expecting array, check each expected element to be contained in the actual array
console.debug(`expecting array ${JSON.stringify(actual[key])} contains ${JSON.stringify(expected[key])}`)
for (let expectedIdx in expected[key]) {
let check = true
if (typeof expected[key][expectedIdx] == 'object') {//if array of objects, recursive call.
check = this.contains(actual[key][expectedIdx], expected[key][expectedIdx])
console.debug(`expecting array item ${JSON.stringify(actual[key][expectedIdx])} contains ${JSON.stringify(expected[key][expectedIdx])}. check: ${check}`)
} else {//if array of primitives, use includes
check = actual[key].includes(expected[key][expectedIdx])
console.debug(`expecting array item ${JSON.stringify(actual[key])} includes ${JSON.stringify(expected[key][expectedIdx])}. check: ${check}`)
}
result = result && check
}
} else {//expecting a "normal" object
let check = this.contains(actual[key], expected[key])
console.debug(`expecting an object from ${actual[key]}. check: ${check}`)
result = result && check
}
} else {//expecting a primitive, direct comparison
console.debug(`expecting ${expected[key]} == ${actual[key]}`)
result = result && (expected[key] == actual[key])
}
})
} catch (err) {
console.error(`failed check: ${err}`)
result = false
}
return result;
}


const containsKeys = function (actual, expected) {
let result = true;
try {
console.debug(`type of expected ${typeof expected} `)
if (!(typeof expected === 'object')) {//primitive type or generic 'object'
let check = (actual == null || (typeof actual) == expected)
console.debug(`checking primitive type directly ${actual} ${expected}: ${check}`)
result = result && check;
} else {
Object.keys(expected).forEach( (key) => {
console.debug(`checking object key ${key}`)
if (expected[key] == "array") { //if we simply expect an array, and we don't care about the content. allows null
let check = actual[key] == null || actual[key] instanceof Array
console.debug(`expecting generic array from ${actual[key]}: ${check}`)
result = result && check
} else {
if (typeof expected[key] === 'object') {
console.debug(`expecting object from ${actual[key]}`)
if (actual[key] instanceof Array) {//if we want to check the content of the array
console.debug(`- that object is an array`)
actual[key].forEach((element) => {
console.debug(`-- checking array element ${element}, should be ${expected[key][0]}`)
let check = this.containsKeys(element, expected[key][0])//compare each element with the "schema" expected
console.debug(`-- checking array element ${element}, should be ${expected[key][0]} resulted: ${check}`)
result = result && check
})
} else {
console.debug(`- that object is an actual object. checking contents`)
let check = this.containsKeys(actual[key], expected[key])
console.debug(`- that object is an actual object checking contents resulted: ${check}`)
result = result && check;//object, but not array
}
} else {
let check = (actual[key] == null || (typeof actual[key]) == expected[key])
console.debug(`checking primitive type nested ${actual[key]} ${expected[key]}: ${check}`)
result = result && check;//primitive type
}
}
})
}

} catch (err) {
console.error(`failed check: ${err}`)
result = false
}
return result;
}


const listOfTypes = function (actual, expected) {
let result = true;
actual.forEach((object) => {
result = result && this.containsKeys(object, expected);
})

return result;
};


const typeOf = function (actual, expected) {
let check = typeof actual == expected
console.debug(`checking primitive type of ${actual} == ${expected}: ${check}`)
return check;
}

const strategies = {
contains,
containsKeys,
listOfTypes,
typeOf
}



module.exports = {
compare
}
Loading

0 comments on commit 851834e

Please sign in to comment.