├── .github └── workflows │ └── ci-module.yml ├── .gitignore ├── API.md ├── LICENSE.md ├── README.md ├── lib ├── index.js ├── safe-json.js └── squeeze.js ├── package.json └── test └── index.js /.github/workflows/ci-module.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: [ubuntu, windows, macos] 15 | node: ['*', '14', '12'] 16 | 17 | runs-on: ${{ matrix.os }}-latest 18 | name: ${{ matrix.os }} node@${{ matrix.node }} 19 | steps: 20 | - uses: actions/checkout@v2 21 | - uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node }} 24 | - name: install 25 | run: npm install 26 | - name: test 27 | run: npm test 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/package-lock.json 3 | 4 | coverage.* 5 | 6 | **/.DS_Store 7 | **/._* 8 | 9 | **/*.pem 10 | 11 | **/.vs 12 | **/.vscode 13 | **/.idea 14 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | 2 | ## Usage 3 | 4 | good-squeeze is a collection of small transform streams. The `Squeeze` stream is useful for filtering events based on the good event options. The `SafeJson` stream is useful for stringifying objects to prevent circular object errors. 5 | 6 | ## Methods 7 | 8 | ### `Squeeze(events, [options])` 9 | 10 | Creates a new Squeeze transform stream where: 11 | 12 | - `events` an object where each key is a valid good event and the value is one of the following: 13 | - `string` - a tag to include when filtering. '*' indicates no filtering. 14 | - `array` - array of tags to filter. `[]` indicates no filtering. 15 | - `object` - an object with the following values 16 | - `include` - string or array representing tag(s) to *include* when filtering 17 | - `exclude` - string or array representing tag(s) to *exclude* when filtering. `exclude` takes precedence over any `include` tags. 18 | - `[options]` configuration object that gets passed to the Node [`Stream.Transform`](http://nodejs.org/api/stream.html#stream_class_stream_transform) constructor. **Note** `objectMode` is always `true` for all `Squeeze` objects. 19 | 20 | The transform stream only passes on events that satisfy the event filtering based on event name and tags. If the upstream event doesn't satisfy the filter, it is not continued down the pipe line. 21 | 22 | ### `Squeeze.subscription(events)` 23 | 24 | A static method on `Squeeze` that creates a new event subscription map where: 25 | 26 | - `events` the same arguments used in the `Squeeze` constructor. 27 | 28 | ```js 29 | const Squeeze = require('good-squeeze'); 30 | 31 | Squeeze.subscription({ log: 'user', ops: '*', request: ['hapi', 'foo'] }); 32 | 33 | // Results in 34 | // { 35 | // log: { include: [ 'user' ], exclude: [] }, 36 | // ops: { include: [], exclude: [] }, 37 | // request: { include: [ 'hapi', 'foo' ], exclude: [] } 38 | // } 39 | 40 | Squeeze.subscription({ log: 'user', ops: { exclude: 'debug' }, request: { include: ['hapi', 'foo'], exclude: 'sensitive' } }); 41 | 42 | // Results in 43 | // { 44 | // log: { include: [ 'user' ], exclude: [] }, 45 | // ops: { include: [], exclude: [ 'debug' ] }, 46 | // request: { include: [ 'hapi', 'foo' ], exclude: [ 'sensitive' ] } 47 | // } 48 | ``` 49 | 50 | Useful for creating an event subscription to be used with `Squeeze.filter` if you do not plan on creating a pipeline coming from good and instead want to manage event filtering manually. 51 | 52 | 53 | ### `Squeeze.filter(subscription, data)` 54 | 55 | Returns `true` if the supplied `data.event` + `data.tags` should be reported based on `subscription` where: 56 | 57 | - `subscription` - a subscription map created by `Squeeze.subscription()`. 58 | - `data` - event object emitted from good/hapi which should contain the following keys: 59 | - `event` - a string representing the event name of `data` 60 | - `tags` - an array of strings representing tags associated with this event. 61 | 62 | ### `SafeJson([options], [stringify])` 63 | 64 | Creates a new SafeJson transform stream where: 65 | 66 | - `[options]` configuration object that gets passed to the Node [`Stream.Transform`](http://nodejs.org/api/stream.html#stream_class_stream_transform) constructor. **Note** `objectMode` is always `true` for all `Squeeze` objects. 67 | - `[stringify]` configuration object for controlling how stringify is handled. 68 | - `separator` - string to append to each message. Defaults to "\n". 69 | - `space` - number of spaces in pretty printed JSON. Doesn't pretty print when set to 0. Defaults to 0. 70 | 71 | The transform stream stringifys the incoming data and pipes it forward. It will not crash in the cases of circular references and will instead include a "~Circular" string in the result. 72 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2020, Sideway Inc, and project contributors 2 | Copyright (c) 2014-2016, Walmart. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | * The names of any contributors may not be used to endorse or promote products derived from this software without specific prior written permission. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # @hapi/good-squeeze 4 | 5 | #### Simple transform stream for event filtering with good. 6 | 7 | **good-squeeze** is part of the **hapi** ecosystem and was designed to work seamlessly with the [hapi web framework](https://hapi.dev) and its other components (but works great on its own or with other frameworks). If you are using a different web framework and find this module useful, check out [hapi](https://hapi.dev) – they work even better together. 8 | 9 | ### Visit the [hapi.dev](https://hapi.dev) Developer Portal for tutorials, documentation, and support 10 | 11 | ## Useful resources 12 | 13 | - [Documentation and API](https://hapi.dev/family/good-squeeze/) 14 | - [Version status](https://hapi.dev/resources/status/#good-squeeze) (builds, dependencies, node versions, licenses, eol) 15 | - [Changelog](https://hapi.dev/family/good-squeeze/changelog/) 16 | - [Project policies](https://hapi.dev/policies/) 17 | - [Free and commercial support options](https://hapi.dev/support/) 18 | 19 | ## Note: this module is being deprecated on December 31st, 2020 due to lack to available support resources. Please consider using [another logging plugin](https://hapi.dev/plugins/#logging). 20 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Squeeze = require('./squeeze'); 4 | const SafeJson = require('./safe-json'); 5 | 6 | 7 | exports.Squeeze = Squeeze; 8 | 9 | exports.SafeJson = SafeJson; 10 | -------------------------------------------------------------------------------- /lib/safe-json.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Stream = require('stream'); 4 | 5 | const Stringify = require('fast-safe-stringify'); 6 | 7 | 8 | const internals = {}; 9 | 10 | 11 | module.exports = class extends Stream.Transform { 12 | 13 | constructor(options, stringify) { 14 | 15 | options = Object.assign({}, options, { 16 | objectMode: true 17 | }); 18 | super(options); 19 | this._stringify = Object.assign({}, { 20 | separator: '\n', 21 | space: 0 22 | }, stringify); 23 | } 24 | 25 | _transform(data, enc, next) { 26 | 27 | next(null, `${Stringify(data, null, this._stringify.space)}${this._stringify.separator}`); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /lib/squeeze.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Stream = require('stream'); 4 | 5 | const Hoek = require('@hapi/hoek'); 6 | 7 | 8 | const internals = {}; 9 | 10 | 11 | module.exports = internals.Squeeze = class extends Stream.Transform { 12 | 13 | constructor(events, options) { 14 | 15 | events = events || {}; 16 | Hoek.assert(typeof events === 'object', 'events must be an object'); 17 | 18 | options = Object.assign({}, options, { 19 | objectMode: true 20 | }); 21 | super(options); 22 | this._subscription = internals.Squeeze.subscription(events); 23 | } 24 | 25 | _transform(data, enc, next) { 26 | 27 | if (internals.Squeeze.filter(this._subscription, data)) { 28 | return next(null, data); 29 | } 30 | 31 | next(null); 32 | } 33 | 34 | static subscription(events) { 35 | 36 | const result = Object.create(null); 37 | const subs = Object.keys(events); 38 | 39 | for (let i = 0; i < subs.length; ++i) { 40 | const key = subs[i]; 41 | const filter = events[key]; 42 | const tags = {}; 43 | 44 | if (filter && (filter.include || filter.exclude)) { 45 | tags.include = internals.Squeeze.toTagArray(filter.include); 46 | tags.exclude = internals.Squeeze.toTagArray(filter.exclude); 47 | } 48 | else { 49 | tags.include = internals.Squeeze.toTagArray(filter); 50 | tags.exclude = []; 51 | } 52 | 53 | result[key.toLowerCase()] = tags; 54 | } 55 | 56 | return result; 57 | } 58 | 59 | static toTagArray(filter) { 60 | 61 | if (Array.isArray(filter) || (filter && filter !== '*')) { 62 | const tags = [].concat(filter); 63 | 64 | // Force everything to be a string 65 | for (let i = 0; i < tags.length; ++i) { 66 | tags[i] = '' + tags[i]; 67 | } 68 | 69 | return tags; 70 | } 71 | 72 | return []; 73 | } 74 | 75 | static filter(subscription, data) { 76 | 77 | const tags = data.tags || []; 78 | const subEventTags = subscription[data.event]; 79 | 80 | // If we aren't interested in this event, break 81 | if (!subEventTags) { 82 | return false; 83 | } 84 | 85 | // If include & exclude is an empty array, we do not want to do any filtering 86 | if (subEventTags.include.length === 0 && (subEventTags.exclude.length === 0 || tags.length === 0)) { 87 | return true; 88 | } 89 | 90 | // Check event tags to see if one of them is in this reports list 91 | if (Array.isArray(tags)) { 92 | let result = false; 93 | for (let i = 0; i < tags.length; ++i) { 94 | const eventTag = tags[i]; 95 | // As soon as an exclude tag matches, exclude the log entry 96 | if (subEventTags.exclude.indexOf(eventTag) > -1) { 97 | return false; 98 | } 99 | 100 | result = result || subEventTags.include.indexOf(eventTag) > -1 || subEventTags.include.length === 0; 101 | } 102 | 103 | return result; 104 | } 105 | 106 | return false; 107 | } 108 | }; 109 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hapi/good-squeeze", 3 | "version": "6.0.0", 4 | "description": "Collection of utility transform streams.", 5 | "repository": "https://github.com/hapijs/good-squeeze.git", 6 | "main": "lib/index.js", 7 | "files": [ 8 | "lib" 9 | ], 10 | "keywords": [ 11 | "good", 12 | "good-reporter", 13 | "transform-stream", 14 | "filtering", 15 | "stream" 16 | ], 17 | "dependencies": { 18 | "@hapi/hoek": "9.x.x", 19 | "fast-safe-stringify": "2.x.x" 20 | }, 21 | "devDependencies": { 22 | "@hapi/boom": "9.x.x", 23 | "@hapi/code": "8.x.x", 24 | "@hapi/lab": "24.x.x", 25 | "@hapi/teamwork": "5.x.x" 26 | }, 27 | "scripts": { 28 | "test": "lab -m 5000 -t 100 -L -a @hapi/code" 29 | }, 30 | "license": "BSD-3-Clause" 31 | } 32 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Stream = require('stream'); 4 | 5 | const Boom = require('@hapi/boom'); 6 | const Code = require('@hapi/code'); 7 | const GoodSqueeze = require('..'); 8 | const Hoek = require('@hapi/hoek'); 9 | const Lab = require('@hapi/lab'); 10 | const Teamwork = require('@hapi/teamwork'); 11 | 12 | 13 | const { describe, it } = exports.lab = Lab.script(); 14 | const expect = Code.expect; 15 | 16 | 17 | const internals = {}; 18 | 19 | 20 | describe('Squeeze', () => { 21 | 22 | it('does not forward events if "filter()" is false', { plan: 1 }, async () => { 23 | 24 | const stream = new GoodSqueeze.Squeeze({ request: '*' }); 25 | const result = []; 26 | 27 | stream.on('data', (data) => { 28 | 29 | result.push(data); 30 | }); 31 | 32 | const team = new Teamwork.Team(); 33 | stream.on('end', () => { 34 | 35 | expect(result).to.equal([{ 36 | event: 'request', 37 | id: 1 38 | }]); 39 | 40 | team.attend(); 41 | }); 42 | 43 | const read = internals.readStream(); 44 | 45 | read.pipe(stream); 46 | 47 | read.push({ event: 'request', id: 1 }); 48 | read.push({ event: 'ops', id: 2 }); 49 | read.push(null); 50 | 51 | await team.work; 52 | }); 53 | 54 | it('remains open as long as the read stream does not end it', { plan: 1 }, async () => { 55 | 56 | const stream = new GoodSqueeze.Squeeze({ request: '*' }); 57 | const result = []; 58 | 59 | stream.on('data', (data) => { 60 | 61 | result.push(data); 62 | }); 63 | 64 | stream.on('end', () => { 65 | 66 | expect.fail('End should never be called'); 67 | }); 68 | 69 | const read = internals.readStream(); 70 | 71 | read.pipe(stream); 72 | 73 | read.push({ event: 'request', id: 1 }); 74 | read.push({ event: 'request', id: 2 }); 75 | 76 | await Hoek.wait(500); 77 | 78 | read.push({ event: 'request', id: 3 }); 79 | read.push({ event: 'request', id: 4 }); 80 | 81 | expect(result).to.equal([ 82 | { event: 'request', id: 1 }, 83 | { event: 'request', id: 2 }, 84 | { event: 'request', id: 3 }, 85 | { event: 'request', id: 4 } 86 | ]); 87 | }); 88 | 89 | it('throws an error if "events" not a truthy object', { plan: 2 }, () => { 90 | 91 | expect(() => { 92 | 93 | new GoodSqueeze.Squeeze('request'); 94 | }).to.throw('events must be an object'); 95 | expect(() => { 96 | 97 | new GoodSqueeze.Squeeze(1); 98 | }).to.throw('events must be an object'); 99 | }); 100 | 101 | it('allows empty event arguments', { plan: 1 }, () => { 102 | 103 | const stream = new GoodSqueeze.Squeeze(null); 104 | expect(stream._subscription).to.equal(Object.create(null)); 105 | }); 106 | 107 | describe('subscription()', () => { 108 | 109 | it('converts *, null, undefined, 0, and false to an empty include/exclude object, indicating all tags are acceptable', { plan: 5 }, () => { 110 | 111 | const tags = ['*', null, undefined, false, 0]; 112 | for (let i = 0; i < tags.length; ++i) { 113 | 114 | const result = GoodSqueeze.Squeeze.subscription({ error: tags[i] }); 115 | 116 | expect(result.error).to.equal({ 117 | include: [], 118 | exclude: [] 119 | }); 120 | } 121 | }); 122 | 123 | it('converts a single tag to an include/exclude object', { plan: 1 }, () => { 124 | 125 | const result = GoodSqueeze.Squeeze.subscription({ error: 'hapi' }); 126 | expect(result.error).to.equal({ 127 | include: ['hapi'], 128 | exclude: [] 129 | }); 130 | }); 131 | 132 | it('converts an array to an include/exclude object', { plan: 1 }, () => { 133 | 134 | const result = GoodSqueeze.Squeeze.subscription({ error: ['hapi', 'error'] }); 135 | expect(result.error).to.equal({ 136 | include: ['hapi', 'error'], 137 | exclude: [] 138 | }); 139 | }); 140 | 141 | it('adds excluded tags to exclude array in map', () => { 142 | 143 | const result = GoodSqueeze.Squeeze.subscription({ error: { exclude: ['sensitive'] } }); 144 | expect(result.error).to.equal({ 145 | include: [], 146 | exclude: ['sensitive'] 147 | }); 148 | }); 149 | }); 150 | 151 | describe('filter()', () => { 152 | 153 | it('returns true if this reporter should report this event type', { plan: 1 }, () => { 154 | 155 | const subscription = GoodSqueeze.Squeeze.subscription({ log: '*' }); 156 | expect(GoodSqueeze.Squeeze.filter(subscription, { event: 'log', tags: ['request', 'server', 'error', 'hapi'] })).to.be.true(); 157 | }); 158 | 159 | it('returns false if this report should not report this event type', { plan: 1 }, () => { 160 | 161 | const subscription = GoodSqueeze.Squeeze.subscription({ log: '*' }); 162 | expect(GoodSqueeze.Squeeze.filter(subscription, { event: 'ops', tags: ['*'] })).to.be.false(); 163 | }); 164 | 165 | it('returns true if the event is matched, but there are not any tags with the data', { plan: 1 }, () => { 166 | 167 | const subscription = GoodSqueeze.Squeeze.subscription({ log: '*' }); 168 | expect(GoodSqueeze.Squeeze.filter(subscription, { event: 'log' })).to.be.true(); 169 | }); 170 | 171 | it('returns false if the subscriber has tags, but the matched event does not have any', { plan: 1 }, () => { 172 | 173 | const subscription = GoodSqueeze.Squeeze.subscription({ error: 'db' }); 174 | expect(GoodSqueeze.Squeeze.filter(subscription, { event: 'error', tags: [] })).to.be.false(); 175 | }); 176 | 177 | it('returns true if the event and tag match', { plan: 1 }, () => { 178 | 179 | const subscription = GoodSqueeze.Squeeze.subscription({ error: ['high', 'medium', 'log'] }); 180 | expect(GoodSqueeze.Squeeze.filter(subscription, { event: 'error', tags: ['hapi', 'high', 'db', 'severe'] })).to.be.true(); 181 | }); 182 | 183 | it('returns false by default', { plan: 1 }, () => { 184 | 185 | const subscription = GoodSqueeze.Squeeze.subscription({ request: 'hapi' }); 186 | expect(GoodSqueeze.Squeeze.filter(subscription, { event: 'request' })).to.be.false(); 187 | }); 188 | 189 | it('returns false if "tags" is not an array', { plan: 1 }, () => { 190 | 191 | const subscription = GoodSqueeze.Squeeze.subscription({ request: 'hapi' }); 192 | expect(GoodSqueeze.Squeeze.filter(subscription, { event: 'request', tags: 'hapi' })).to.be.false(); 193 | }); 194 | 195 | it('returns true if this reporter should report this event type (advanced)', { plan: 1 }, () => { 196 | 197 | const subscription = GoodSqueeze.Squeeze.subscription({ log: { include: '*' } }); 198 | expect(GoodSqueeze.Squeeze.filter(subscription, { event: 'log', tags: ['request', 'server', 'hapi', 'debug'] })).to.be.true(); 199 | }); 200 | 201 | it('returns false if this reporter should not report this event with both include and exclude tags defined', { plan: 1 }, () => { 202 | 203 | const subscription = GoodSqueeze.Squeeze.subscription({ log: { include: 'request', exclude: 'debug' } }); 204 | expect(GoodSqueeze.Squeeze.filter(subscription, { event: 'log', tags: ['request', 'server', 'hapi', 'debug'] })).to.be.false(); 205 | }); 206 | 207 | it('returns false if this reporter should not report this event with exclude tags defined', { plan: 1 }, () => { 208 | 209 | const subscription = GoodSqueeze.Squeeze.subscription({ log: { exclude: 'debug' } }); 210 | expect(GoodSqueeze.Squeeze.filter(subscription, { event: 'log', tags: ['request', 'server', 'hapi', 'debug'] })).to.be.false(); 211 | }); 212 | 213 | it('returns true if this reporter should report this event with only exclude tags defined', { plan: 1 }, () => { 214 | 215 | const subscription = GoodSqueeze.Squeeze.subscription({ log: { exclude: 'debug' } }); 216 | expect(GoodSqueeze.Squeeze.filter(subscription, { event: 'log', tags: ['request', 'server', 'hapi'] })).to.be.true(); 217 | }); 218 | 219 | it('returns true if this reporter should report this event with only exclude tags defined in case no tags are present', { plan: 1 }, () => { 220 | 221 | const subscription = GoodSqueeze.Squeeze.subscription({ log: { exclude: 'debug' } }); 222 | expect(GoodSqueeze.Squeeze.filter(subscription, { event: 'log' })).to.be.true(); 223 | }); 224 | }); 225 | }); 226 | 227 | describe('SafeJson', () => { 228 | 229 | it('safely handles circular references in incoming data', { plan: 1 }, async () => { 230 | 231 | let result = ''; 232 | const stream = new GoodSqueeze.SafeJson(); 233 | const read = internals.readStream(); 234 | 235 | const message = { 236 | x: 1 237 | }; 238 | message.y = message; 239 | 240 | stream.on('data', (data) => { 241 | 242 | result += data; 243 | }); 244 | 245 | const team = new Teamwork.Team(); 246 | stream.on('end', () => { 247 | 248 | expect(result).to.equal('{"x":1,"y":"[Circular]"}\n{"foo":"bar"}\n'); 249 | team.attend(); 250 | }); 251 | 252 | read.pipe(stream); 253 | 254 | read.push(message); 255 | read.push({ foo: 'bar' }); 256 | read.push(null); 257 | 258 | await team.work; 259 | }); 260 | 261 | it('pretty print JSON with space when specified', { plan: 1 }, async () => { 262 | 263 | let result = ''; 264 | const stream = new GoodSqueeze.SafeJson({}, { space: 2 }); 265 | const read = internals.readStream(); 266 | 267 | stream.on('data', (data) => { 268 | 269 | result += data; 270 | }); 271 | 272 | const team = new Teamwork.Team(); 273 | stream.on('end', () => { 274 | 275 | expect(result).to.equal('{\n "foo": "bar"\n}\n'); 276 | team.attend(); 277 | }); 278 | 279 | read.pipe(stream); 280 | 281 | read.push({ foo: 'bar' }); 282 | read.push(null); 283 | await team.work; 284 | }); 285 | 286 | it('adds a separator value when specified', { plan: 1 }, async () => { 287 | 288 | let result = ''; 289 | const stream = new GoodSqueeze.SafeJson({}, { separator: '#' }); 290 | const read = internals.readStream(); 291 | 292 | stream.on('data', (data) => { 293 | 294 | result += data; 295 | }); 296 | 297 | const team = new Teamwork.Team(); 298 | stream.on('end', () => { 299 | 300 | expect(result).to.equal('{"foo":"bar"}#{"bar":"baz"}#'); 301 | team.attend(); 302 | }); 303 | 304 | read.pipe(stream); 305 | 306 | read.push({ foo: 'bar' }); 307 | read.push({ bar: 'baz' }); 308 | read.push(null); 309 | await team.work; 310 | }); 311 | 312 | it('serializes incoming buffer data', { plan: 1 }, async () => { 313 | 314 | let result = ''; 315 | const stream = new GoodSqueeze.SafeJson({}, { separator: '#' }); 316 | const read = internals.readStream(); 317 | 318 | stream.on('data', (data) => { 319 | 320 | result += data; 321 | }); 322 | 323 | const team = new Teamwork.Team(); 324 | stream.on('end', () => { 325 | 326 | expect(result).to.equal('{\"type\":\"Buffer\",\"data\":[116,101,115,116,45,48]}#{\"type\":\"Buffer\",\"data\":[116,101,115,116,45,49]}#'); 327 | team.attend(); 328 | }); 329 | 330 | read.pipe(stream); 331 | 332 | read.push(Buffer.from('test-0')); 333 | read.push(Buffer.from('test-1')); 334 | read.push(null); 335 | await team.work; 336 | }); 337 | 338 | it('serializes incoming string data', { plan: 1 }, async () => { 339 | 340 | let result = ''; 341 | const stream = new GoodSqueeze.SafeJson({}, { separator: '#' }); 342 | const read = internals.readStream(); 343 | 344 | stream.on('data', (data) => { 345 | 346 | result += data; 347 | }); 348 | 349 | const team = new Teamwork.Team(); 350 | stream.on('end', () => { 351 | 352 | expect(result).to.equal('"test-0"#"test-1"#'); 353 | team.attend(); 354 | }); 355 | 356 | read.pipe(stream); 357 | 358 | read.push('test-0'); 359 | read.push('test-1'); 360 | read.push(null); 361 | await team.work; 362 | }); 363 | 364 | it('serializes incoming boom objects', { plan: 1 }, async () => { 365 | 366 | let result = ''; 367 | const stream = new GoodSqueeze.SafeJson({}, { separator: '#' }); 368 | const read = internals.readStream(); 369 | 370 | stream.on('data', (data) => { 371 | 372 | result += data; 373 | }); 374 | 375 | const team = new Teamwork.Team(); 376 | stream.on('end', () => { 377 | 378 | expect(result).to.equal('{"data":null,"isBoom":true,"isServer":true,"output":{"statusCode":500,"payload":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred"},"headers":{}},"isDeveloperError":true}#'); 379 | team.attend(); 380 | }); 381 | 382 | read.pipe(stream); 383 | 384 | read.push(Boom.badImplementation('test-message')); 385 | read.push(null); 386 | await team.work; 387 | }); 388 | }); 389 | 390 | 391 | internals.readStream = function () { 392 | 393 | const stream = new Stream.Readable({ objectMode: true }); 394 | stream._read = () => { }; 395 | return stream; 396 | }; 397 | --------------------------------------------------------------------------------