Skip to content

Commit

Permalink
Merge pull request #271 from trescube/added-tests-for-various-helper-…
Browse files Browse the repository at this point in the history
…functions

added unit tests for various helper functions
  • Loading branch information
cars10 authored Dec 5, 2024
2 parents e4a6268 + f9d6c74 commit 6eff18c
Show file tree
Hide file tree
Showing 7 changed files with 464 additions and 0 deletions.
43 changes: 43 additions & 0 deletions tests/unit/helpers/beautify.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, it, expect } from 'vitest'

import { beautify } from '../../../src/helpers/beautify'

describe.concurrent('helpers/elasticsearchAdapter.ts', () => {
describe.concurrent('beautify.ts beautify', () => {
it('should return empty string when supplied with empty string', () => {
expect(beautify('')).toBe('')
})

it('should return undefined when supplied with undefined', () => {
expect(beautify(undefined as unknown as string)).toBe(undefined)
})

it('should return null when supplied with null', () => {
expect(beautify(null as unknown as string)).toBe(null)
})

it('should return false when supplied with false', () => {
expect(beautify(false as unknown as string)).toBe(false)
})

it('should return 0 when supplied with 0', () => {
expect(beautify(0 as unknown as string)).toBe(0)
})

it('should return empty string when supplied with empty string', () => {
const text = '{"a":123456789012345678901234567890}'

const normalJSONStrinigified = JSON.stringify(JSON.parse(text))

expect(normalJSONStrinigified).toBe('{"a":1.2345678901234568e+29}')

expect(beautify(text)).toBe('{\n "a": 1.2345678901234567890123456789e+29\n}')
})

it('should return the input string if not parseable as JSON', () => {
const text = 'this { is not } json'

expect(beautify(text)).toBe(text)
})
})
})
80 changes: 80 additions & 0 deletions tests/unit/helpers/elasticsearchAdapter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { describe, it, expect } from 'vitest'

import { buildFetchAuthHeader, addTrailingSlash, uriWithCredentials } from '../../../src/helpers/elasticsearchAdapter'

describe.concurrent('helpers/elasticsearchAdapter.ts', () => {
describe.concurrent('elasticsearchAdapter.ts buildFetchAuthHeader', () => {
it('should base64-encode username and password when both are non-zero length', () => {
const result = buildFetchAuthHeader('username', 'password')

const [schema, encoded] = result.split(' ')

expect(schema).toBe('Basic')
expect(decodeURIComponent(escape(atob(encoded))).split(':')).toEqual(['username', 'password'])
})

it('should base64-encode only username when non-zero length and password is zero-length', () => {
const result = buildFetchAuthHeader('username', '')

const [schema, encoded] = result.split(' ')

expect(schema).toBe('Basic')
expect(decodeURIComponent(escape(atob(encoded)))).toEqual('username')
})

it('should return ApiKey schema and plaintext password when username is zero-length and password is non-zero length', () => {
const result = buildFetchAuthHeader('', 'password')

const [schema, apiKey] = result.split(' ')

expect(schema).toBe('ApiKey')
expect(apiKey).toBe('password')
})

it('should return empty string when both username and password are zero-length', () => {
const result = buildFetchAuthHeader('', '')

expect(result).toBe('')
})
})

describe.concurrent('elasticsearchAdapter.ts addTrailingSlash', () => {
it('should not append a / if uri already ends with a /', () => {
const uri = 'http://localhost:9200/'
const result = addTrailingSlash(uri)
expect(result).toBe(uri)
})

it('should append a / if uri does not end with a /', () => {
const uri = 'http://localhost:9200'
const result = addTrailingSlash(uri)
expect(result).toBe(uri + '/')
})
})

describe.concurrent('elasticsearchAdapter.ts uriWithCredentials', () => {
it('should stringify URI with username and password when username is non-zero length', () => {
const uri = 'http://localhost:9200/'
const result = uriWithCredentials(uri, 'username', 'password')
expect(result).toBe('http://username:password@localhost:9200/')
})

it('should not stringify URI with username and password when username is zero length', () => {
const uri = 'http://localhost:9200/'
const result = uriWithCredentials(uri, '', 'password')
expect(result).toBe(uri)
})

it('should replace embedded username and password when new username is non-zero length', () => {
const uri = 'http://original_username:original_password@localhost:9200/'
const result = uriWithCredentials(uri, 'username', 'password')
expect(result).toBe('http://username:password@localhost:9200/')
})

it('should replace username and retain password when new username is zero length', () => {
const uri = 'http://original_username:original_password@localhost:9200/'
const result = uriWithCredentials(uri, '', 'password')
expect(result).toBe('http://:original_password@localhost:9200/')
})
})
})
71 changes: 71 additions & 0 deletions tests/unit/helpers/flatten.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { describe, it, expect } from 'vitest'

import { flattenObj } from '../../../src/helpers/flatten'

describe.concurrent('helpers/flatten.ts', () => {
describe.concurrent('flatten.ts flattenObj', () => {
it('should flatten all root objects to dot notation', () => {
const obj = {
a: 1,
b: {
c: {
d: 2,
}
},
e: [3, 4, { f: { g: 5 } }],
h: {
i: 6
}
}

expect(flattenObj(obj)).toStrictEqual({
a: 1,
'b.c.d': 2,
e: [3, 4, { f: { g: 5 } }],
'h.i': 6
})
})

it('should throw a RangeError when input object contains a circular reference', () => {
const obj = {
a: 1
}

obj['b'] = obj

expect(() => flattenObj(obj)).toThrowError(RangeError)
})

it('should flatten null to an empty object', () => {
expect(flattenObj(null)).toStrictEqual({})
})

it('should flatten undefined to an empty object', () => {
expect(flattenObj(undefined)).toStrictEqual({})
})

it('should flatten number to an empty object', () => {
expect(flattenObj(17)).toStrictEqual({})
})

it('should flatten boolean to an empty object', () => {
expect(flattenObj(true)).toStrictEqual({})
})

it('should flatten array to an index+character object', () => {
expect(flattenObj(['a', 'b'])).toStrictEqual({
0: 'a',
1: 'b'
})
})

it('should flatten string to index+character object', () => {
expect(flattenObj('asdf')).toStrictEqual({
0: 'a',
1: 's',
2: 'd',
3: 'f'
})
})
})
})
23 changes: 23 additions & 0 deletions tests/unit/helpers/newCluster.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, it, expect } from 'vitest'

import { newElasticsearchCluster } from '../../../src/helpers/newCluster'
import { DEFAULT_CLUSTER_NAME, DEFAULT_CLUSTER_URI } from '../../../src/consts.ts'

describe.concurrent('helpers/newCluster.ts', () => {
describe.concurrent('newCluster.ts newElasticsearchCluster', () => {
it('should return empty string when supplied with empty string', () => {
expect(newElasticsearchCluster()).toEqual({
name: DEFAULT_CLUSTER_NAME,
distribution: '',
username: '',
password: '',
uri: DEFAULT_CLUSTER_URI,
clusterName: '',
version: '',
majorVersion: '',
uuid: '',
status: ''
})
})
})
})
64 changes: 64 additions & 0 deletions tests/unit/helpers/nodes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { describe, it, expect } from 'vitest'

import { nodeRoleTitle } from '../../../src/helpers/nodes'

const NEWLINE = '\r\n'

describe.concurrent('helpers/nodes.ts', () => {
describe.concurrent('nodes.ts nodeRoleTitle', () => {
it('should return empty string when supplied with empty string', () => {
expect(nodeRoleTitle('')).toBe('')
})

it('should return all role titles when input string contains them all', () => {
// include all lowercase latin alphabet characters and hyphen character for future proofing
const nodes = new Set(nodeRoleTitle('abcdefghijklmnopqrstuvwxyz-').trimEnd().split(NEWLINE))

expect(nodes.size).toBe(13)

expect(nodes).toContain('c - cold node')
expect(nodes).toContain('d - data node')
expect(nodes).toContain('f - frozen node')
expect(nodes).toContain('h - hot node')
expect(nodes).toContain('i - ingest node')
expect(nodes).toContain('l - machine learning node')
expect(nodes).toContain('m - master-eligible node')
expect(nodes).toContain('r - remote cluster client node')
expect(nodes).toContain('s - content node')
expect(nodes).toContain('t - transform node')
expect(nodes).toContain('v - voting-only node')
expect(nodes).toContain('w - warm node')
expect(nodes).toContain('coordinating nodes')
})

it('should only include a single role title when input is a single role character', () => {
const supportedRoles = new Map([
['c','c - cold node'],
['d','d - data node'],
['f','f - frozen node'],
['h','h - hot node'],
['i','i - ingest node'],
['l','l - machine learning node'],
['m','m - master-eligible node'],
['r','r - remote cluster client node'],
['s','s - content node'],
['t','t - transform node'],
['v','v - voting-only node'],
['w','w - warm node'],
['-','coordinating nodes']
])

for (const [role, title] of supportedRoles) {
expect(nodeRoleTitle(role).trimEnd()).toBe(title)
}
})

it('should return an empty string for all unsupported latin-1 lowercase letters', () => {
const unsupportedRoleTypes = ['a', 'b', 'e', 'g', 'j', 'k', 'n', 'o', 'p', 'q', 'u', 'x', 'y', 'z']

for (const role of unsupportedRoleTypes) {
expect(nodeRoleTitle(role)).toBe('')
}
})
})
})
88 changes: 88 additions & 0 deletions tests/unit/helpers/search.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { describe, it, expect } from 'vitest'

import { sortableField } from '../../../src/helpers/search'
import { DEFAULT_SORTABLE_COLUMNS } from '../../../src/consts'

describe.concurrent('helpers/search.ts', () => {
describe.concurrent('search.ts sortableField', () => {
it('should return column name for all default sortable columns', () => {
for (const column of DEFAULT_SORTABLE_COLUMNS) {
expect(sortableField(column, null)).toBe(column)
}
})

it('should return null when fieldName is not a default sortable column name and property is falsy', () => {
for (const falsyProperty of [null, undefined, 0, '', false, NaN]) {
expect(sortableField('non-default sortable column', falsyProperty)).toBe(null)
}
})

it('should return fieldName when property.type is a sortable type', () => {
for (const type of ['long', 'integer', 'double', 'float', 'date', 'boolean', 'keyword']) {
const property = {
type: 'keyword'
}

expect(sortableField('random fieldName', property)).toBe('random fieldName')
}
})

it('should return fieldName with \'.keyword\' appended when property.fields.keyword is truthy', () => {
const property = {
fields: {
keyword: 'some value'
}
}

expect(sortableField('random fieldName', property)).toBe('random fieldName.keyword')
})

it('should return fieldName with \'.keyword\' appended when property.fields.keyword is truthy', () => {
const property = {
fields: {
keyword: 'some value'
}
}

expect(sortableField('random fieldName', property)).toBe('random fieldName.keyword')
})

it('should return fieldName appended with subfield type when subfield type is sortable', () => {
for (const type of ['long', 'integer', 'double', 'float', 'date', 'boolean', 'keyword']) {
const property = {
fields: {
a: {
type
},
b: 2
}
}

expect(sortableField('random fieldName', property)).toBe('random fieldName.a')
}
})

it('should return null when subfield type is non-sortable', () => {
const property = {
fields: {
a: {
type: 'non-sortable type'
}
}
}

expect(sortableField('random fieldName', property)).toBe(null)
})

it('should return property has fields but none of them have a type', () => {
const property = {
fields: {
a: {},
b: {}
}
}

expect(sortableField('random fieldName', property)).toBe(null)
})
})
})
Loading

0 comments on commit 6eff18c

Please sign in to comment.