console-web-node 0.0.12 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1 +1,41 @@
1
- # console-web-node
1
+ # console-web-node
2
+
3
+ Guarda em memória as últimas linhas escritas no console (stdout/stderr) de um servidor Node com Express (ou NestJS sobre Express), e permite acompanhá-las de outra máquina: primeiro a janela guardada, depois as linhas novas em tempo real.
4
+
5
+ ## Servidor
6
+
7
+ ```js
8
+ import consoleWebNode from 'console-web-node'
9
+ import moment from 'moment'
10
+
11
+ consoleWebNode({ express: app, moment })
12
+
13
+ // NestJS: o logger já coloca data e hora
14
+ consoleWebNode({ express: app.getHttpAdapter().getInstance(), maxLines: 100000 })
15
+ ```
16
+
17
+ | opção | padrão | |
18
+ |---|---|---|
19
+ | `express` | — | app ou router express onde é registrada a rota dos logs |
20
+ | `moment` | — | instância do moment para colocar data e hora nas linhas (sem ela as linhas ficam como foram escritas, para loggers que já colocam data e hora) |
21
+ | `logsPath` | `/api/x/logs` | caminho da rota (relativo ao `express` informado) |
22
+ | `maxLines` | `10000` | quantidade de linhas guardadas em memória |
23
+ | `consoleTimestamp` | `true` | escreve também no console as linhas com data e hora |
24
+
25
+ ## Acompanhar os logs
26
+
27
+ ```sh
28
+ export CONSOLE_WEB_NODE_KEY=<chave>
29
+ npx console-web-node meusistema.com.br # https://meusistema.com.br/api/x/logs
30
+ npx console-web-node https://meusistema.com.br/x/logs # outro caminho
31
+ ```
32
+
33
+ Rodando na pasta do projeto que usa a biblioteca, o `npx` usa a versão instalada nele (a mesma do servidor).
34
+
35
+ ## Chave de acesso
36
+
37
+ ```sh
38
+ npx console-web-node --nova-chave
39
+ ```
40
+
41
+ Gera uma chave aleatória (para a variável `CONSOLE_WEB_NODE_KEY`) e o hash dela, que vai em `KEY_HASH` em `src/auth.js`. Sem hash configurado nenhuma chave é aceita.
package/package.json CHANGED
@@ -1,9 +1,19 @@
1
1
  {
2
2
  "name": "console-web-node",
3
- "version": "0.0.12",
4
- "description": "",
3
+ "version": "0.1.0",
4
+ "description": "Guarda em memória as últimas linhas do console de um servidor Node/Express e permite acompanhá-las de outra máquina (npx console-web-node <servidor>)",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
7
+ "bin": {
8
+ "console-web-node": "src/client.js"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "!src/**/*.test.js"
13
+ ],
14
+ "engines": {
15
+ "node": ">=14"
16
+ },
7
17
  "scripts": {
8
18
  "start": "nodemon",
9
19
  "test:watch": "nodemon -x mocha -p 'src/**/*.test.js'",
@@ -21,10 +31,7 @@
21
31
  "homepage": "https://github.com/LeonardoRomeiro/console-web-node#readme",
22
32
  "devDependencies": {
23
33
  "chai": "^4.2.0",
24
- "mocha": "^8.1.1",
25
- "nodemon": "^2.0.4"
26
- },
27
- "dependencies": {
28
- "minio": "^7.0.16"
34
+ "mocha": "^10.8.2",
35
+ "nodemon": "^3.1.14"
29
36
  }
30
37
  }
package/src/auth.js ADDED
@@ -0,0 +1,21 @@
1
+ import crypto from 'crypto'
2
+
3
+ // sha256 (base64) da chave de acesso aos logs, gerado com `npx console-web-node --nova-chave`
4
+ // a chave é aleatória (256 bits): mesmo com o hash público não dá para descobri-la por força bruta
5
+ // vazio: nenhuma chave é aceita
6
+ export const KEY_HASH = 'PgYhxkg8Kk8BXArYKM8a6Y/ME7/cYn3COpBoAf9lm58='
7
+
8
+ const sha256 = (value) => crypto.createHash('sha256').update(value).digest()
9
+
10
+ // confere o cabeçalho Authorization (Bearer <chave>) com o hash da chave
11
+ export function isAuthorized(authorization, keyHash = KEY_HASH) {
12
+ const match = /^Bearer (\S+)$/.exec(authorization || '')
13
+ const expected = Buffer.from(keyHash, 'base64')
14
+ if (!match || expected.length !== 32) return false
15
+ return crypto.timingSafeEqual(sha256(match[1]), expected)
16
+ }
17
+
18
+ export function newKey() {
19
+ const key = crypto.randomBytes(32).toString('hex')
20
+ return { key, hash: sha256(key).toString('base64') }
21
+ }
package/src/client.js ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ // acompanha os logs de um servidor com console-web-node: mostra a janela de linhas guardada e segue as novas
3
+ // uso: npx console-web-node <servidor ou url> (chave na variável de ambiente CONSOLE_WEB_NODE_KEY)
4
+ // npx console-web-node --nova-chave
5
+ import http from 'http'
6
+ import https from 'https'
7
+ import { newKey } from './auth.js'
8
+
9
+ const KEY_ENV = 'CONSOLE_WEB_NODE_KEY'
10
+ const RETRY_MS = 5000
11
+ const RESPONSE_TIMEOUT_MS = 60000 // o servidor responde em até 30s mesmo sem linhas novas
12
+
13
+ const [target] = process.argv.slice(2)
14
+
15
+ if (target === '--nova-chave') {
16
+ const { key, hash } = newKey()
17
+ console.log(`Chave (guarde na variável de ambiente ${KEY_ENV} de quem vai acompanhar os logs):\n ${key}\n`)
18
+ console.log(`Hash (substitua KEY_HASH em src/auth.js e publique uma nova versão):\n ${hash}`)
19
+ } else if (!target || target.startsWith('-')) {
20
+ console.error('uso: console-web-node <servidor ou url> (ex.: meusistema.com.br, https://meusistema.com.br/api/x/logs)')
21
+ console.error(' console-web-node --nova-chave')
22
+ process.exitCode = 1
23
+ } else if (!process.env[KEY_ENV]) {
24
+ console.error(`informe a chave de acesso aos logs na variável de ambiente ${KEY_ENV}`)
25
+ process.exitCode = 1
26
+ } else {
27
+ const url = new URL(/^https?:\/\//.test(target) ? target : `https://${target}`)
28
+ if (url.pathname === '/') url.pathname = '/api/x/logs'
29
+ follow(url, process.env[KEY_ENV])
30
+ }
31
+
32
+ function follow(url, key, cursor = '') {
33
+ const started = Date.now()
34
+ let received = 0
35
+ let finished = false // erros da requisição e da resposta podem chegar juntos: segue uma vez só
36
+ if (cursor) url.searchParams.set('cursor', cursor)
37
+ const continueAfter = (wait, nextCursor) => {
38
+ if (finished) return
39
+ finished = true
40
+ setTimeout(() => follow(url, key, nextCursor), wait)
41
+ }
42
+ const retry = (error) => {
43
+ const reason = error.message || error.code || String(error) // falha de conexão pode vir só com o código (ex.: ECONNREFUSED)
44
+ if (!finished) console.error(`\n[console-web-node] ${reason}, tentando novamente em ${RETRY_MS / 1000}s...`)
45
+ continueAfter(RETRY_MS, cursor)
46
+ }
47
+
48
+ const request = (url.protocol === 'http:' ? http : https).get(url, { headers: { Authorization: `Bearer ${key}` } }, (res) => {
49
+ if (res.statusCode === 401) {
50
+ console.error(`[console-web-node] chave recusada pelo servidor, confira a variável de ambiente ${KEY_ENV}`)
51
+ process.exit(1)
52
+ }
53
+ const next = res.headers['x-cursor']
54
+ if (res.statusCode !== 200 || !next) {
55
+ res.resume()
56
+ return retry(new Error(`resposta inesperada do servidor (HTTP ${res.statusCode})`))
57
+ }
58
+ if (cursor && next.split('.')[0] !== cursor.split('.')[0]) console.log('\n[console-web-node] servidor reiniciado, logs desde o início:\n')
59
+
60
+ res.setEncoding('utf8')
61
+ res.on('data', (data) => {
62
+ received += data.length
63
+ process.stdout.write(data)
64
+ })
65
+ res.on('error', retry)
66
+ res.on('end', () => {
67
+ // resposta vazia imediata não deveria acontecer: espera um pouco para não ficar em loop
68
+ continueAfter(!received && Date.now() - started < 1000 ? 1000 : 0, next)
69
+ })
70
+ })
71
+ request.setTimeout(RESPONSE_TIMEOUT_MS, () => request.destroy(new Error('sem resposta do servidor')))
72
+ request.on('error', retry)
73
+ }
package/src/index.js CHANGED
@@ -1,165 +1,39 @@
1
- import { CircularBuffer } from './utils/circular-buffer.js'
2
- import minio from 'minio'
3
- import crypto from 'crypto'
4
- import eventEmitter from 'events'
5
-
6
- const LOGS_BUCKET = 'logs'
7
- const READ_TIMEOUT = 30000
8
- const LISTNER_TIMEOUT = 60000
9
- const BROADCAST_TOKEN = 'broadcast'
10
- const events = new eventEmitter()
11
-
12
- export default async function ConsoleWebNode({ consoleTimestamp = true, maxLines = 10000, express = null, name = 'logs', moment, logsPath = '/api/x/logs' }) {
13
- const circularBuffer = CircularBuffer(maxLines)
14
-
15
- let writeToFile = () => {}
16
-
17
- if (process.env.MINIO_URL) {
18
- const minioClient = new minio.Client({
19
- endPoint: process.env.MINIO_URL,
20
- accessKey: process.env.MINIO_ACCESS_KEY,
21
- secretKey: process.env.MINIO_SECRET_KEY,
22
- useSSL: true
23
- })
24
-
25
- !await minioClient.bucketExists(LOGS_BUCKET) && await minioClient.makeBucket(LOGS_BUCKET)
26
-
27
- await minioClient.getObject(LOGS_BUCKET, name)
28
- .then((stream) => new Promise((resolve) => {
29
- const data = []
30
- stream.on('data', data.push.bind(data))
31
- stream.on('end', resolve.bind(null, data))
32
- }))
33
- .then((file) => Buffer.concat(file).toString().split('\n').forEach(line => circularBuffer.push(line + '\n')))
34
- .catch(() => { })
35
-
36
- writeToFile = () => {
37
- writeToFile._timeout && clearTimeout(writeToFile._timeout)
38
- writeToFile._timeout = setTimeout(() => minioClient.putObject(LOGS_BUCKET, name, circularBuffer.toArray().join('')), 200)
39
- }
40
- }
41
-
42
- express.get(logsPath, async (req, res) => {
43
- if (!hasListner(req.query.u)) {
44
- if (!checkPass(req.query.p)) res.end()
45
- setListner(req.query.u)
46
- return res.send(circularBuffer.toArray().join(''))
47
- }
48
- setListner(req.query.u)
49
- const ls = await readListner(req.query.u)
50
- res.send(ls.join(''))
51
- })
52
- express.post(logsPath, async (req, res) => {
53
- if (hasListner(req.query.u)) clearLastLogs(req.query.u)
54
- res.end()
55
- })
56
-
57
- const StdoutWrite = global.process.stdout.write.bind(global.process.stdout)
58
- const StderrWrite = global.process.stderr.write.bind(global.process.stderr)
59
-
60
- global.process.stdout.write = function($str) {
61
- if (typeof $str !== 'string') return StdoutWrite($str)
62
-
63
- const strTime = _setTimestamp(moment, $str)
64
-
65
- if ($str[$str.length - 1] === '\n') {
66
- const strFormat = _formatLogString(strTime)
67
- consoleTimestamp ? StdoutWrite(strFormat) : StdoutWrite($str)
68
- broadcast(strFormat)
69
- circularBuffer.push(strFormat)
70
- writeToFile()
71
- } else {
72
- consoleTimestamp ? StdoutWrite(strTime) : StdoutWrite($str)
73
- broadcast(strTime)
74
- }
75
- }
76
-
77
- global.process.stderr.write = function($str) {
78
- if (typeof $str !== 'string') return StderrWrite($str)
79
-
80
- const strTime = _setTimestamp(moment, $str)
81
-
82
- if ($str[$str.length - 1] === '\n') {
83
- const strFormat = _formatLogErrString(strTime)
84
- consoleTimestamp ? StderrWrite(strFormat) : StderrWrite($str)
85
- broadcast(strFormat)
86
- circularBuffer.push(strFormat)
87
- writeToFile()
88
- } else {
89
- consoleTimestamp ? StderrWrite(strTime) : StderrWrite($str)
90
- broadcast(strTime)
91
- }
1
+ import { createLogBuffer } from './log-buffer.js'
2
+ import { createLogsHandler } from './logs-handler.js'
3
+
4
+ // guarda em memória as últimas maxLines linhas escritas no stdout/stderr e as disponibiliza
5
+ // na rota logsPath do app express para o cliente (npx console-web-node <servidor>)
6
+ // moment: coloca data e hora em cada linha (sem ele as linhas ficam como foram escritas, ex.: logger que já tem data e hora)
7
+ // consoleTimestamp: escreve também no console as linhas com data e hora (false: o console recebe o texto original)
8
+ export default function ConsoleWebNode({ express, moment, logsPath = '/api/x/logs', maxLines = 10000, consoleTimestamp = true }) {
9
+ if (!express) throw new Error('console-web-node: informe em express o app onde registrar a rota dos logs')
10
+
11
+ const buffer = createLogBuffer(maxLines)
12
+ express.get(logsPath, createLogsHandler(buffer))
13
+
14
+ const format = createFormatter(moment)
15
+ intercept(process.stdout, buffer, format, consoleTimestamp, (line) => line)
16
+ intercept(process.stderr, buffer, format, consoleTimestamp, (line) => `\x1b[31m${line}\x1b[0m`)
17
+ }
18
+
19
+ // só linhas completas (terminadas em \n) vão para os logs; escritas parciais (ex.: progresso com \r) só para o console
20
+ function intercept(stream, buffer, format, consoleTimestamp, color) {
21
+ const write = stream.write.bind(stream)
22
+ stream.write = function(chunk, ...args) {
23
+ if (typeof chunk !== 'string') return write(chunk, ...args)
24
+ if (!chunk.endsWith('\n')) return write(consoleTimestamp ? format.partial(chunk) : chunk, ...args)
25
+ const line = color(format.line(chunk))
26
+ buffer.push(line)
27
+ return write(consoleTimestamp ? line : chunk, ...args)
92
28
  }
93
29
  }
94
30
 
95
- const listners = {}
96
- function broadcast($str) {
97
- Object.keys(listners).forEach((key) => {
98
- listners[key].logs.push($str)
99
- })
100
-
101
- broadcast._timeout && clearTimeout(broadcast._timeout)
102
- broadcast._timeout = setTimeout(() => events.emit(BROADCAST_TOKEN), 0)
103
- }
104
-
105
- function setListner(listner) {
106
- listners[listner] = listners[listner] || { logs: [], lastLogs: [] }
107
-
108
- listners[listner]._setTimeout && clearTimeout(listners[listner]._setTimeout)
109
- listners[listner]._setTimeout = setTimeout(() => {
110
- listners[listner]._readTimeout && clearTimeout(listners[listner]._readTimeout)
111
- listners[listner]._readClear && events.off(BROADCAST_TOKEN, listners[listner]._readClear)
112
- delete listners[listner]
113
- }, LISTNER_TIMEOUT)
114
- }
115
-
116
- function clearLastLogs(listner) {
117
- listners[listner].lastLogs = []
118
- }
119
-
120
- function readListner(listner) {
121
- return new Promise((resolve) => {
122
- if (listners[listner].lastLogs[0] || listners[listner].logs[0]) return resolve(read(listner))
123
-
124
- listners[listner]._readTimeout && clearTimeout(listners[listner]._readTimeout)
125
- listners[listner]._readClear && events.off(BROADCAST_TOKEN, listners[listner]._readClear)
126
-
127
- listners[listner]._readClear = () => {
128
- clearTimeout(listners[listner]._readTimeout)
129
- resolve(read(listner))
130
- }
131
-
132
- listners[listner]._readTimeout = setTimeout(() => {
133
- events.off(BROADCAST_TOKEN, listners[listner]._readClear)
134
- resolve([])
135
- }, READ_TIMEOUT)
136
-
137
- events.once(BROADCAST_TOKEN, listners[listner]._readClear)
138
- })
139
- }
140
-
141
- function read(listner) {
142
- listners[listner].lastLogs = listners[listner].lastLogs.concat(listners[listner].logs)
143
- listners[listner].logs = []
144
- return listners[listner].lastLogs
145
- }
146
-
147
- function hasListner(listner) {
148
- return !!listners[listner]
149
- }
150
-
151
- function checkPass(pass) {
152
- return crypto.createHash('sha1').update(pass).digest('base64') === 'rJzrVISFf5EOCkdA3DkVpId9yZo='
153
- }
154
-
155
- function _setTimestamp(moment, $str) {
156
- return `${moment().format('DD MMM YYYY HH:mm:ss')} ${$str}`
157
- }
158
-
159
- function _formatLogString($str) {
160
- return `${$str.slice(0, -1).replace(/\n/g, '\n ') + '\n'}`
161
- }
162
-
163
- function _formatLogErrString($str) {
164
- return `\x1b[31m${_formatLogString($str)}\x1b[0m`
31
+ // data e hora no início, com as linhas seguintes de uma mensagem com várias linhas alinhadas depois dela
32
+ export function createFormatter(moment) {
33
+ if (!moment) return { line: (text) => text, partial: (text) => text }
34
+ const timestamp = (text) => `${moment().format('DD MMM YYYY HH:mm:ss')} ${text}`
35
+ return {
36
+ line: (text) => timestamp(text).slice(0, -1).replace(/\n/g, '\n ') + '\n',
37
+ partial: timestamp,
38
+ }
165
39
  }
@@ -0,0 +1,52 @@
1
+ import crypto from 'crypto'
2
+
3
+ // janela com as últimas linhas do log, numeradas em sequência desde o início do processo
4
+ // o cursor (instância.sequência) indica ao cliente de onde continuar a leitura;
5
+ // a instância muda quando o processo reinicia, e aí o cliente recebe a janela inteira do processo novo
6
+ export function createLogBuffer(maxLines) {
7
+ const instance = crypto.randomBytes(4).toString('hex')
8
+ const waiting = new Set()
9
+ let notifying = false
10
+ let lines = []
11
+ let total = 0
12
+
13
+ const notify = () => {
14
+ notifying = false
15
+ const waiters = Array.from(waiting)
16
+ waiting.clear()
17
+ waiters.forEach((done) => done())
18
+ }
19
+
20
+ return {
21
+ push(line) {
22
+ lines.push(line)
23
+ total++
24
+ if (lines.length >= maxLines * 2) lines = lines.slice(-maxLines) // descarta as antigas de uma vez, não a cada linha
25
+ if (waiting.size && !notifying) { // várias linhas seguidas vão na mesma resposta
26
+ notifying = true
27
+ setImmediate(notify)
28
+ }
29
+ },
30
+
31
+ // linhas depois do cursor (a janela inteira se o cursor for de outra instância, inválido ou ausente) e o próximo cursor
32
+ since(cursor) {
33
+ const [cursorInstance, sequence] = String(cursor || '').split('.')
34
+ const seen = cursorInstance === instance && /^\d+$/.test(sequence) ? Number(sequence) : -Infinity
35
+ const count = Math.min(total - seen, lines.length, maxLines)
36
+ return { lines: count > 0 ? lines.slice(-count) : [], cursor: `${instance}.${total}` }
37
+ },
38
+
39
+ // resolve quando chegar uma linha nova ou depois de timeoutMs
40
+ waitForLines(timeoutMs) {
41
+ return new Promise((resolve) => {
42
+ const done = () => {
43
+ clearTimeout(timer)
44
+ waiting.delete(done)
45
+ resolve()
46
+ }
47
+ const timer = setTimeout(done, timeoutMs)
48
+ waiting.add(done)
49
+ })
50
+ },
51
+ }
52
+ }
@@ -0,0 +1,21 @@
1
+ import { isAuthorized as defaultIsAuthorized } from './auth.js'
2
+
3
+ // GET dos logs: devolve as linhas depois do cursor (?cursor=) e o próximo cursor no cabeçalho X-Cursor
4
+ // sem linhas novas, espera até waitMs por elas (long polling) antes de responder vazio
5
+ export function createLogsHandler(buffer, { waitMs = 30000, isAuthorized = defaultIsAuthorized } = {}) {
6
+ return async (req, res) => {
7
+ if (!isAuthorized(req.headers.authorization)) {
8
+ res.writeHead(401)
9
+ return res.end()
10
+ }
11
+ const cursor = new URL(req.url, 'http://localhost').searchParams.get('cursor')
12
+ let result = buffer.since(cursor)
13
+ if (!result.lines.length) {
14
+ await buffer.waitForLines(waitMs)
15
+ result = buffer.since(cursor)
16
+ }
17
+ if (res.destroyed) return // cliente desconectou enquanto esperava
18
+ res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', 'Cache-Control': 'no-store', 'X-Cursor': result.cursor })
19
+ res.end(result.lines.join(''))
20
+ }
21
+ }
@@ -1,39 +0,0 @@
1
- export function CircularBuffer(size) {
2
- let currentIndex = size - 1
3
- const bufferArray = Array(size)
4
- const indexTable = _newBufferNextIndexTable(size)
5
- return {
6
- push(value) {
7
- currentIndex = _push(currentIndex, indexTable, bufferArray, value)
8
- return this
9
- },
10
- toArray() { return _toArray(currentIndex, indexTable, bufferArray) }
11
- }
12
- }
13
-
14
- export function _newBufferNextIndexTable(size) {
15
- const result = {}
16
- for (let index = 0; index < size; index++) {
17
- result[index] = index + 1
18
- }
19
- result[size - 1] = 0
20
- return result
21
- }
22
-
23
- export function _push(currentIndex, indexTable, bufferArray, value) {
24
- const nextIndex = indexTable[currentIndex]
25
- bufferArray[nextIndex] = value
26
- return nextIndex
27
- }
28
-
29
- export function _toArray(currentIndex, indexTable, bufferArray) {
30
- currentIndex = indexTable[currentIndex]
31
- if (typeof bufferArray[currentIndex] === 'undefined') return bufferArray.slice(0, currentIndex)
32
-
33
- const result = []
34
- for (const value of bufferArray) {
35
- result.push(bufferArray[currentIndex])
36
- currentIndex = indexTable[currentIndex]
37
- }
38
- return result
39
- }
@@ -1,102 +0,0 @@
1
- import chai from 'chai'
2
- import { _newBufferNextIndexTable, _push, _toArray, CircularBuffer } from './circular-buffer.js'
3
-
4
- describe(import.meta.url, function() {
5
-
6
- describe(_newBufferNextIndexTable.name, function() {
7
- it('deve retornar uma tabela de próximos índices', function() {
8
- chai.expect(_newBufferNextIndexTable(4)).to.deep.equal({ 0: 1, 1: 2, 2: 3, 3: 0 })
9
- })
10
- })
11
-
12
- describe(_push.name, function() {
13
- it('deve retornar o array com o valor inserido na posicão 5', function() {
14
- const value = 'teste'
15
- const index = 3
16
- const indexTable = _newBufferNextIndexTable(6)
17
- const bufferArray = Array(6)
18
- const nextIndex = _push(index, indexTable, bufferArray, value)
19
- chai.expect(nextIndex).to.equal(4)
20
- chai.expect(bufferArray[4]).to.equal(value)
21
- })
22
- it('deve retornar o array com o valor inserido na última posição', function() {
23
- const value = 'teste'
24
- const index = 4
25
- const indexTable = _newBufferNextIndexTable(6)
26
- const bufferArray = Array(6)
27
- const nextIndex = _push(index, indexTable, bufferArray, value)
28
- chai.expect(nextIndex).to.equal(5)
29
- chai.expect(bufferArray[bufferArray.length - 1]).to.equal(value)
30
- })
31
- it('deve retornar o array com o valor inserido na primeira posicão', function() {
32
- const value = 'teste'
33
- const index = 5
34
- const indexTable = _newBufferNextIndexTable(6)
35
- const bufferArray = Array(6)
36
- const nextIndex = _push(index, indexTable, bufferArray, value)
37
- chai.expect(nextIndex).to.equal(0)
38
- chai.expect(bufferArray[0]).to.equal(value)
39
- })
40
- })
41
-
42
- describe(_toArray.name, function() {
43
- it('deve retornar um array com a órdem dos itens do buffer circular', function() {
44
- const bufferArray = [4, 5, 0, 1, 2, 3]
45
- const indexTable = _newBufferNextIndexTable(6)
46
- const currentIndex = 1
47
- const expectedArray = [0, 1, 2, 3, 4, 5]
48
- const resultArray = _toArray(currentIndex, indexTable, bufferArray)
49
- chai.expect(resultArray).to.deep.equal(expectedArray)
50
- })
51
- it('deve retornar um array incompleto', function() {
52
- const bufferArray = [4, 5, , , , ]
53
- const indexTable = _newBufferNextIndexTable(6)
54
- const currentIndex = 1
55
- const expectedArray = [4, 5]
56
- const resultArray = _toArray(currentIndex, indexTable, bufferArray)
57
- chai.expect(resultArray).to.deep.equal(expectedArray)
58
- })
59
- })
60
-
61
- describe(CircularBuffer.name, function() {
62
- it('deve retornar um array preenchido 1', function() {
63
- const circularBuffer = CircularBuffer(6)
64
- const expectedArray = [0, 1, 2, 3, 4, 5]
65
- circularBuffer.push(0)
66
- circularBuffer.push(1)
67
- circularBuffer.push(2)
68
- circularBuffer.push(3)
69
- circularBuffer.push(4)
70
- circularBuffer.push(5)
71
- chai.expect(circularBuffer.toArray()).to.deep.equal(expectedArray)
72
- })
73
- it('deve retornar um array preenchido 2', function() {
74
- const circularBuffer = CircularBuffer(6)
75
- const expectedArray = [3, 4, 5, 6, 7, 8]
76
- circularBuffer.push(0)
77
- circularBuffer.push(1)
78
- circularBuffer.push(2)
79
- circularBuffer.push(3)
80
- circularBuffer.push(4)
81
- circularBuffer.push(5)
82
- circularBuffer.push(6)
83
- circularBuffer.push(7)
84
- circularBuffer.push(8)
85
- chai.expect(circularBuffer.toArray()).to.deep.equal(expectedArray)
86
- })
87
- it('deve retornar um array preenchido 3', function() {
88
- const circularBuffer = CircularBuffer(6)
89
- const expectedArray = [6, 7, 8]
90
- circularBuffer.push(6)
91
- circularBuffer.push(7)
92
- circularBuffer.push(8)
93
- chai.expect(circularBuffer.toArray()).to.deep.equal(expectedArray)
94
- })
95
- it('deve retornar um array vazio', function() {
96
- const circularBuffer = CircularBuffer(6)
97
- const expectedArray = []
98
- chai.expect(circularBuffer.toArray()).to.deep.equal(expectedArray)
99
- })
100
- })
101
-
102
- })