Typescript port (#35)
* converted all files to ts, updated errors, except for __audits in test/basic.ts * passes tslint working on compiling * working on build and travis * Update travis file * Update package.json * add linting command * add rimraf to cleanup script * fix spelling of clutter * clean up configurations and add comments * clean up package scripts * update path param * fix new linting and ts errors
This commit is contained in:
Родитель
834cec3658
Коммит
e45b1a8691
|
@ -57,3 +57,5 @@ typings/
|
|||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# typescript compiled files
|
||||
dist/
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
language: node_js
|
||||
|
||||
before_script:
|
||||
- npm run compile
|
||||
|
||||
script:
|
||||
- npm run test
|
||||
|
||||
node_js:
|
||||
- "node"
|
||||
- "lts/*"
|
5
index.js
5
index.js
|
@ -1,5 +0,0 @@
|
|||
module.exports = {
|
||||
parsers: require('./parsers'),
|
||||
validators: require('./validators'),
|
||||
auditer: require('./audit')
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
18
package.json
18
package.json
|
@ -2,9 +2,12 @@
|
|||
"name": "platform-chaos",
|
||||
"version": "1.0.0",
|
||||
"description": "A node sdk for building services capable of injecting chaos into PaaS offerings",
|
||||
"main": "index.js",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"test": "mocha"
|
||||
"lint": "npx tslint --project .",
|
||||
"test": "npm run lint && mocha dist/test/",
|
||||
"compile": "npm run cleanup && npx tsc",
|
||||
"cleanup": "rimraf dist/"
|
||||
},
|
||||
"keywords": [
|
||||
"azure",
|
||||
|
@ -14,6 +17,11 @@
|
|||
"author": "microsoft",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/lodash.isequal": "^4.5.3",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/shimmer": "^1.0.1",
|
||||
"@types/sinon": "^5.0.2",
|
||||
"@types/uuid": "^3.4.4",
|
||||
"eslint": "^5.4.0",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
|
@ -22,7 +30,11 @@
|
|||
"eslint-plugin-standard": "^4.0.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"mocha": "^5.2.0",
|
||||
"sinon": "^6.2.0"
|
||||
"rimraf": "^2.6.2",
|
||||
"sinon": "^6.2.0",
|
||||
"tslint": "^5.11.0",
|
||||
"tslint-config-standard": "^8.0.1",
|
||||
"typescript": "^3.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"shimmer": "^1.2.0",
|
||||
|
|
|
@ -1,43 +1,58 @@
|
|||
const shimmer = require('shimmer')
|
||||
const assert = require('assert')
|
||||
const os = require('os')
|
||||
const uuidv4 = require('uuid/v4')
|
||||
import * as assert from 'assert'
|
||||
import * as os from 'os'
|
||||
import * as shimmer from 'shimmer'
|
||||
import * as uuidv4 from 'uuid'
|
||||
|
||||
/* interface implementation of Audit defined in
|
||||
* https://github.com/Azure/platform-chaos/wiki/Auditing
|
||||
* */
|
||||
export interface IAudit {
|
||||
auditId: string,
|
||||
date: string,
|
||||
eventName: string,
|
||||
extensionLog: string[],
|
||||
resources: string,
|
||||
system: string
|
||||
}
|
||||
|
||||
interface IAuditOptions {
|
||||
eventName: string,
|
||||
resources: string
|
||||
}
|
||||
|
||||
const auditSystem = `${os.hostname()}-${os.platform()}`
|
||||
let auditQueue = []
|
||||
let auditQueue: IAudit[] = []
|
||||
|
||||
const audit = (extensionLogArgs, auditOptions) => {
|
||||
const audit = (extensionLogArgs, auditOptions: IAuditOptions) => {
|
||||
const { eventName, resources } = auditOptions
|
||||
|
||||
auditQueue.push({
|
||||
auditId: uuidv4(),
|
||||
eventName: eventName,
|
||||
system: auditSystem,
|
||||
date: new Date().toISOString(),
|
||||
resources: resources,
|
||||
extensionLog: typeof extensionLogArgs === 'string' ? [ extensionLogArgs ] : Array.from(extensionLogArgs)
|
||||
eventName,
|
||||
extensionLog: typeof extensionLogArgs === 'string' ? [ extensionLogArgs ] : Array.from(extensionLogArgs),
|
||||
resources,
|
||||
system: auditSystem
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
audit: audit,
|
||||
initialize: (context, opts) => {
|
||||
export default {
|
||||
audit,
|
||||
initialize: (context: any, opts: IAuditOptions) => {
|
||||
assert(opts, 'Options object must be defined')
|
||||
assert(typeof opts.eventName === 'string', 'Event name must be a string')
|
||||
assert(typeof opts.resources === 'string', 'Resources must be a string')
|
||||
|
||||
const auditOptions = opts
|
||||
|
||||
auditQueue = []
|
||||
|
||||
shimmer.wrap(context, 'log', function (original) {
|
||||
shimmer.wrap(context, 'log', (original: (message: any) => void) => {
|
||||
return function () {
|
||||
audit(arguments, auditOptions)
|
||||
return original.apply(this, arguments)
|
||||
}
|
||||
})
|
||||
|
||||
shimmer.wrap(context, 'done', function (original) {
|
||||
shimmer.wrap(context, 'done', (original: (err: any, propertyBag: any) => void) => {
|
||||
return function () {
|
||||
const audits = auditQueue
|
||||
|
||||
|
@ -47,10 +62,10 @@ module.exports = {
|
|||
}
|
||||
} else if (typeof context.res.body === 'string') {
|
||||
const body = JSON.parse(context.res.body)
|
||||
body['__audits'] = audits
|
||||
body.__audits = audits
|
||||
context.res.body = JSON.stringify(body)
|
||||
} else if (typeof context.res.body === 'object') {
|
||||
context.res.body['__audits'] = audits
|
||||
context.res.body.__audits = audits
|
||||
}
|
||||
/*
|
||||
* If context.res.body is not of type string or object
|
|
@ -0,0 +1,9 @@
|
|||
import Auditers from './audit'
|
||||
import Parsers from './parsers'
|
||||
import Validators from './validators'
|
||||
|
||||
export default {
|
||||
auditer: Auditers,
|
||||
parsers: Parsers,
|
||||
validators: Validators
|
||||
}
|
|
@ -1,15 +1,16 @@
|
|||
const validate = require('./validators')
|
||||
import validate from './validators'
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
accessTokenToCredentials: (req) => {
|
||||
// ensure we have a valid accessToken before proceeding
|
||||
validate.accessToken(req)
|
||||
|
||||
// this implements the contract defined by msRestAzure
|
||||
// see https://github.com/Azure/azure-sdk-for-node/blob/0a52678ea7b3a24a478975f7169fc30e9fc9759e/runtime/ms-rest-azure/lib/credentials/deviceTokenCredentials.js
|
||||
// tslint:disable-next-line:max-line-length
|
||||
// see https://github.com/Azure/azure-sdk-for-node/blob/master/runtime/ms-rest-azure/lib/credentials/deviceTokenCredentials.js
|
||||
return {
|
||||
signRequest: (webResource, callback) => {
|
||||
webResource.headers['Authorization'] = req.body.accessToken
|
||||
webResource.headers.Authorization = req.body.accessToken
|
||||
callback(null)
|
||||
}
|
||||
}
|
||||
|
@ -19,11 +20,11 @@ module.exports = {
|
|||
validate.resources(req)
|
||||
|
||||
// parse the resources into objects
|
||||
return req.body.resources.map(r => r.split('/')).map(r => {
|
||||
return req.body.resources.map((r) => r.split('/')).map((r) => {
|
||||
return {
|
||||
subscriptionId: r[0],
|
||||
resourceGroupName: r[1],
|
||||
resourceName: r[2]
|
||||
resourceName: r[2],
|
||||
subscriptionId: r[0]
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,195 +1,206 @@
|
|||
const assert = require('assert')
|
||||
const index = require('../')
|
||||
const sinon = require('sinon')
|
||||
const isEqual = require('lodash.isequal')
|
||||
|
||||
/* eslint-env node, mocha */
|
||||
|
||||
describe('platform-chaos', () => {
|
||||
it('is named properly', () => {
|
||||
assert.equal(require('../package.json').name, 'platform-chaos')
|
||||
assert.equal(require('../package-lock.json').name, 'platform-chaos')
|
||||
})
|
||||
it('parses resources', () => {
|
||||
assert.throws(() => {
|
||||
index.validators.resources({
|
||||
body: {
|
||||
resources: [1]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
assert.throws(() => {
|
||||
index.validators.resources({
|
||||
body: {
|
||||
resources: ['']
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
assert.throws(() => {
|
||||
index.validators.resources({
|
||||
body: {
|
||||
resources: ['one/two/three/four']
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const instance = index.parsers.resourcesToObjects({
|
||||
body: {
|
||||
resources: ['sub/rg/resource']
|
||||
}
|
||||
})[0]
|
||||
|
||||
assert.equal(instance.subscriptionId, 'sub')
|
||||
assert.equal(instance.resourceGroupName, 'rg')
|
||||
assert.equal(instance.resourceName, 'resource')
|
||||
})
|
||||
|
||||
it('parses accessTokens', () => {
|
||||
// invalid at
|
||||
assert.throws(() => {
|
||||
index.validators.accessToken({
|
||||
body: {
|
||||
accessToken: 1
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// empty resourceIds
|
||||
assert.throws(() => {
|
||||
index.validators.accessToken({
|
||||
body: {
|
||||
accessToken: 'valid type'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// invalid resourceIds type
|
||||
assert.throws(() => {
|
||||
index.validators.accessToken({
|
||||
body: {
|
||||
accessToken: 'valid type'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// invalid resourceIds format
|
||||
assert.throws(() => {
|
||||
index.validators.accessToken({
|
||||
body: {
|
||||
accessToken: 'valid type'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// valid
|
||||
const expectedAccessToken = 'Bearer 12345234r2'
|
||||
const instance = index.parsers.accessTokenToCredentials({
|
||||
body: {
|
||||
accessToken: expectedAccessToken
|
||||
}
|
||||
})
|
||||
|
||||
const res = {
|
||||
headers: {}
|
||||
}
|
||||
|
||||
instance.signRequest(res, () => {})
|
||||
|
||||
assert.equal(res.headers['Authorization'], expectedAccessToken)
|
||||
})
|
||||
|
||||
it('audits correctly', () => {
|
||||
function contextLog () {
|
||||
// in reality this would be `console.log(...arguments)`
|
||||
// but in order to not cluter test we noop this
|
||||
return () => null
|
||||
}
|
||||
function contextDone () {
|
||||
return () => null
|
||||
}
|
||||
|
||||
const contextLogSpy = sinon.spy(contextLog)
|
||||
const contextDoneSpy = sinon.spy(contextDone)
|
||||
|
||||
const context = {
|
||||
log: contextLogSpy,
|
||||
done: contextDoneSpy,
|
||||
res: { body: {} }
|
||||
}
|
||||
|
||||
index.auditer.initialize(context, {
|
||||
eventName: 'testEvent',
|
||||
resources: 'testResource'
|
||||
})
|
||||
|
||||
const logItem1 = {
|
||||
'prop1': 'important information',
|
||||
'anotherProp': 'more important info'
|
||||
}
|
||||
const logItem2 = 'Hello, World!'
|
||||
const logItem3 = ['abc', { a: 12 }]
|
||||
|
||||
context.log(logItem1)
|
||||
context.log(logItem2)
|
||||
context.log(...logItem3)
|
||||
|
||||
context.res.body = {
|
||||
message: 'I am writing to the body'
|
||||
}
|
||||
|
||||
context.done()
|
||||
|
||||
assert(contextLogSpy.called, 'context.log should be called')
|
||||
assert(contextDoneSpy.called, 'context.done should be called')
|
||||
|
||||
const body = context.res.body
|
||||
|
||||
assert(typeof body === 'object', 'context.res.body should exist as an object')
|
||||
assert(body.hasOwnProperty('__audits'), 'body contains __audits property')
|
||||
assert(isEqual(body.__audits[0].extensionLog[0], logItem1), 'log item 1 is added to audit correctly')
|
||||
assert(isEqual(body.__audits[1].extensionLog[0], logItem2), 'log item 2 is added to audit correctly')
|
||||
assert(isEqual(body.__audits[2].extensionLog, logItem3), 'log item 3 is added to audit correctly')
|
||||
})
|
||||
|
||||
it('allows user to audit directly', () => {
|
||||
function contextLog () {
|
||||
// in reality this would be `console.log(...arguments)`
|
||||
// but in order to not cluter test we noop this
|
||||
return () => null
|
||||
}
|
||||
function contextDone () {
|
||||
return () => null
|
||||
}
|
||||
|
||||
const contextLogSpy = sinon.spy(contextLog)
|
||||
const contextDoneSpy = sinon.spy(contextDone)
|
||||
|
||||
const context = {
|
||||
log: contextLogSpy,
|
||||
done: contextDoneSpy,
|
||||
res: { body: {} }
|
||||
}
|
||||
|
||||
const opts = {
|
||||
eventName: 'testEvent',
|
||||
resources: 'testResource'
|
||||
}
|
||||
|
||||
index.auditer.initialize(context, opts)
|
||||
|
||||
index.auditer.audit('Hello, World!', opts)
|
||||
|
||||
context.done()
|
||||
|
||||
assert(contextLogSpy.notCalled, 'context.log should not be called')
|
||||
assert(contextDoneSpy.called, 'context.done should be called')
|
||||
|
||||
const body = context.res.body
|
||||
|
||||
assert(typeof body === 'object', 'context.res.body should exist as an object')
|
||||
assert(body.hasOwnProperty('__audits'), 'body contains __audits property')
|
||||
assert(body.__audits[0].extensionLog[0] === 'Hello, World!', 'log item 1 is added to audit correctly')
|
||||
})
|
||||
})
|
||||
import * as assert from 'assert'
|
||||
import * as sinon from 'sinon'
|
||||
|
||||
import isEqual = require('lodash.isequal')
|
||||
|
||||
import { IAudit } from '../audit'
|
||||
import index from '../index'
|
||||
|
||||
interface IHeader {
|
||||
Authorization?: string
|
||||
}
|
||||
|
||||
interface IBody {
|
||||
__audits?: IAudit[]
|
||||
}
|
||||
|
||||
describe('platform-chaos', () => {
|
||||
it('is named properly', () => {
|
||||
assert.strictEqual(require('../../package.json').name, 'platform-chaos')
|
||||
assert.strictEqual(require('../../package-lock.json').name, 'platform-chaos')
|
||||
})
|
||||
it('parses resources', () => {
|
||||
assert.throws(() => {
|
||||
index.validators.resources({
|
||||
body: {
|
||||
resources: [1]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
assert.throws(() => {
|
||||
index.validators.resources({
|
||||
body: {
|
||||
resources: ['']
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
assert.throws(() => {
|
||||
index.validators.resources({
|
||||
body: {
|
||||
resources: ['one/two/three/four']
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const instance = index.parsers.resourcesToObjects({
|
||||
body: {
|
||||
resources: ['sub/rg/resource']
|
||||
}
|
||||
})[0]
|
||||
|
||||
assert.strictEqual(instance.subscriptionId, 'sub')
|
||||
assert.strictEqual(instance.resourceGroupName, 'rg')
|
||||
assert.strictEqual(instance.resourceName, 'resource')
|
||||
})
|
||||
|
||||
it('parses accessTokens', () => {
|
||||
// invalid at
|
||||
assert.throws(() => {
|
||||
index.validators.accessToken({
|
||||
body: {
|
||||
accessToken: 1
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// empty resourceIds
|
||||
assert.throws(() => {
|
||||
index.validators.accessToken({
|
||||
body: {
|
||||
accessToken: 'valid type'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// invalid resourceIds type
|
||||
assert.throws(() => {
|
||||
index.validators.accessToken({
|
||||
body: {
|
||||
accessToken: 'valid type'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// invalid resourceIds format
|
||||
assert.throws(() => {
|
||||
index.validators.accessToken({
|
||||
body: {
|
||||
accessToken: 'valid type'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// valid
|
||||
const expectedAccessToken = 'Bearer 12345234r2'
|
||||
const instance = index.parsers.accessTokenToCredentials({
|
||||
body: {
|
||||
accessToken: expectedAccessToken
|
||||
}
|
||||
})
|
||||
|
||||
const res = {
|
||||
headers: {} as IHeader
|
||||
}
|
||||
|
||||
instance.signRequest(res, () => null)
|
||||
|
||||
assert.strictEqual(res.headers.Authorization, expectedAccessToken)
|
||||
})
|
||||
|
||||
it('audits correctly', () => {
|
||||
function contextLog () {
|
||||
// in reality this would be `console.log(...arguments)`
|
||||
// but in order to not clutter test we noop this
|
||||
return () => null
|
||||
}
|
||||
function contextDone () {
|
||||
return () => null
|
||||
}
|
||||
|
||||
const contextLogSpy = sinon.spy(contextLog)
|
||||
const contextDoneSpy = sinon.spy(contextDone)
|
||||
|
||||
const context = {
|
||||
done: contextDoneSpy,
|
||||
log: contextLogSpy,
|
||||
res: { body: {} }
|
||||
}
|
||||
|
||||
index.auditer.initialize(context, {
|
||||
eventName: 'testEvent',
|
||||
resources: 'testResource'
|
||||
})
|
||||
|
||||
const logItem1 = {
|
||||
anotherProp: 'more important info',
|
||||
prop1: 'important information'
|
||||
}
|
||||
const logItem2 = 'Hello, World!'
|
||||
const logItem3 = ['abc', { a: 12 }]
|
||||
|
||||
context.log(logItem1)
|
||||
context.log(logItem2)
|
||||
context.log(...logItem3)
|
||||
|
||||
context.res.body = {
|
||||
message: 'I am writing to the body'
|
||||
}
|
||||
|
||||
context.done()
|
||||
|
||||
assert(contextLogSpy.called, 'context.log should be called')
|
||||
assert(contextDoneSpy.called, 'context.done should be called')
|
||||
|
||||
const body: IBody = context.res.body
|
||||
|
||||
assert(body.hasOwnProperty('__audits'), 'body contains __audits property')
|
||||
assert(isEqual(body.__audits && body.__audits[0].extensionLog[0], logItem1),
|
||||
'log item 1 is added to audit correctly')
|
||||
assert(isEqual(body.__audits && body.__audits[1].extensionLog[0], logItem2),
|
||||
'log item 2 is added to audit correctly')
|
||||
assert(isEqual(body.__audits && body.__audits[2].extensionLog, logItem3),
|
||||
'log item 3 is added to audit correctly')
|
||||
})
|
||||
|
||||
it('allows user to audit directly', () => {
|
||||
function contextLog () {
|
||||
// in reality this would be `console.log(...arguments)`
|
||||
// but in order to not cluter test we noop this
|
||||
return () => null
|
||||
}
|
||||
function contextDone () {
|
||||
return () => null
|
||||
}
|
||||
|
||||
const contextLogSpy = sinon.spy(contextLog)
|
||||
const contextDoneSpy = sinon.spy(contextDone)
|
||||
|
||||
const context = {
|
||||
done: contextDoneSpy,
|
||||
log: contextLogSpy,
|
||||
res: { body: {} }
|
||||
}
|
||||
|
||||
const opts = {
|
||||
eventName: 'testEvent',
|
||||
resources: 'testResource'
|
||||
}
|
||||
|
||||
index.auditer.initialize(context, opts)
|
||||
|
||||
index.auditer.audit('Hello, World!', opts)
|
||||
|
||||
context.done()
|
||||
|
||||
assert(contextLogSpy.notCalled, 'context.log should not be called')
|
||||
assert(contextDoneSpy.called, 'context.done should be called')
|
||||
|
||||
const body: IBody = context.res.body
|
||||
|
||||
assert(body.hasOwnProperty('__audits'), 'body contains __audits property')
|
||||
assert(body.__audits && body.__audits[0].extensionLog[0] === 'Hello, World!',
|
||||
'log item 1 is added to audit correctly')
|
||||
})
|
||||
})
|
|
@ -1,6 +1,6 @@
|
|||
const assert = require('assert')
|
||||
import * as assert from 'assert'
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
accessToken: (req) => {
|
||||
assert.ok(req.body.accessToken)
|
||||
assert.ok(typeof req.body.accessToken === 'string')
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es6"],
|
||||
"module": "commonjs",
|
||||
"outDir": "dist",
|
||||
"sourceMap": true,
|
||||
"target": "es6",
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": [
|
||||
"tslint:recommended",
|
||||
"tslint-config-standard"
|
||||
]
|
||||
}
|
Загрузка…
Ссылка в новой задаче