Skip to content

Commit

Permalink
feat: change station object
Browse files Browse the repository at this point in the history
differentiate station response object from the object that is return to the user

BREAKING CHANGE: station object is changed
  • Loading branch information
ivandotv committed Nov 3, 2020
1 parent 332ff40 commit 16edcd2
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 131 deletions.
46 changes: 37 additions & 9 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const StationSearchType = {
byTagExact: 'byTagExact'
} as const

export type Station = {
export type StationResponse = {
changeuuid: string
stationuuid: string
name: string
Expand All @@ -44,24 +44,52 @@ export type Station = {
url_resolved: string
homepage: string
favicon: string
tags: string[]
tags: string
country: string
countrycode: string
state: string
language: string[]
language: string
votes: number
lastchangetime: Date
lastchangetime: string
codec: string
bitrate: number
hls: boolean
lastcheckok: boolean
lastchecktime: Date
lastcheckoktime: Date
clicktimestamp: Date
hls: number
lastcheckok: number
lastchecktime: string
lastlocalchecktime: string
lastcheckoktime: string
clicktimestamp: string
clickcount: number
clicktrend: number
}

export type Station = {
changeId: string
id: string
name: string
url: string
urlResolved: string
homepage: string
favicon: string
tags: string[]
country: string
countryCode: string
state: string
language: string[]
votes: number
lastChangeTime: Date
codec: string
bitrate: number
hls: boolean
lastCheckOk: boolean
lastCheckTime: Date
lastCheckOkTime: Date
lastLocalCheckTime: Date
clickTimestamp: Date
clickCount: number
clickTrend: number
}

export type StationQuery = {
offset?: number
limit?: number
Expand Down
62 changes: 42 additions & 20 deletions src/radioBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import {
CountryStateResult,
Query,
Station,
StationQuery
StationQuery,
StationResponse
} from './constants'

type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U
type StationResponse = Overwrite<Station, { tags: string; language: string }>

export class RadioBrowserApi {
protected baseUrl = 'https://fr1.api.radio-browser.info/json'

Expand Down Expand Up @@ -126,7 +124,6 @@ export class RadioBrowserApi {
query?: StationQuery,
fetchConfig?: RequestInit
): Promise<Station[]> {
// searchType = searchType.toLowerCase() as keyof typeof StationSearchType
if (!StationSearchType[searchType]) {
throw new Error(`search type does not exist: ${searchType}`)
}
Expand All @@ -143,21 +140,46 @@ export class RadioBrowserApi {

protected normalizeStations(stations: StationResponse[]): Station[] {
const result = []
const duplicateNames: { [key: string]: boolean } = {}

for (const rawStation of stations) {
// guard against results having same stations (same names) under different id
const station = ({ ...rawStation } as unknown) as Station

if (duplicateNames[station.name.toLowerCase()]) continue

duplicateNames[rawStation.name.toLowerCase()] = true

station.tags = [...new Set(rawStation.tags.split(','))].filter(
(tag) => tag.length > 0 && tag.length < 10
) // there are tags that are complete sentences

station.language = rawStation.language.split(',')
const duplicates: { [key: string]: boolean } = {}

for (const response of stations) {
const nameAndUrl = `${response.name
.toLowerCase()
.trim()}${response.url.toLowerCase().trim()}`

// guard against results having the same stations under different id's
if (duplicates[nameAndUrl]) continue

duplicates[nameAndUrl] = true

const station: Station = {
changeId: response.changeuuid,
id: response.stationuuid,
name: response.name,
url: response.url,
urlResolved: response.url_resolved,
homepage: response.homepage,
favicon: response.favicon,
country: response.country,
countryCode: response.countrycode,
state: response.state,
votes: response.votes,
codec: response.codec,
bitrate: response.bitrate,
clickCount: response.clickcount,
clickTrend: response.clicktrend,
hls: Boolean(response.hls),
lastCheckOk: Boolean(response.lastcheckok),
lastChangeTime: new Date(response.lastchangetime),
lastCheckOkTime: new Date(response.lastcheckoktime),
clickTimestamp: new Date(response.clicktimestamp),
lastLocalCheckTime: new Date(response.lastlocalchecktime),
language: response.language.split(','),
lastCheckTime: new Date(response.lastchecktime),
tags: [...new Set(response.tags.split(','))].filter(
(tag) => tag.length > 0 && tag.length < 10
) // drop duplicates and tags over 10 characters
}

result.push(station)
}
Expand Down
96 changes: 44 additions & 52 deletions tests/radioBrowser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { StationQuery } from '../dist/types'
import { Query } from '../src'
import { StationSearchType } from '../src/constants'
import { RadioBrowserApi } from '../src/radioBrowser'
import { mockStation, mockStationResponse } from './utils/mockStation'
import { getMockStation, getMockResponse } from './utils/mockStation'

const globalTest = {
fetch: (nodeFetch as unknown) as typeof fetch
Expand Down Expand Up @@ -329,7 +329,7 @@ describe('Radio Browser', () => {

const headerName = 'x-jest-test'
const headerValue = '1'
const mockResult = [mockStationResponse]
const mockResult = [getMockResponse()]
const language = 'ger'
const query = { order: 'name', reverse: true }

Expand Down Expand Up @@ -358,7 +358,7 @@ describe('Radio Browser', () => {
)

expect(scope.isDone()).toBe(true)
expect(result).toEqual([mockStation])
expect(result).toEqual([getMockStation()])
})

test('by tag', async () => {
Expand All @@ -367,7 +367,7 @@ describe('Radio Browser', () => {

const headerName = 'x-jest-test'
const headerValue = '1'
const mockResult = [mockStationResponse]
const mockResult = [getMockResponse()]
const tag = 'jazz'
const query = { order: 'name', reverse: true }

Expand Down Expand Up @@ -396,7 +396,7 @@ describe('Radio Browser', () => {
)

expect(scope.isDone()).toBe(true)
expect(result).toEqual([mockStation])
expect(result).toEqual([getMockStation()])
})
test('Throw if station search type does not exist', async () => {
expect.assertions(1)
Expand All @@ -421,7 +421,7 @@ describe('Radio Browser', () => {

const headerName = 'x-jest-test'
const headerValue = '1'
const mockResult = [mockStationResponse]
const mockResult = [getMockResponse()]
const query = { order: 'name', reverse: true }

const scope = nock(baseUrl, {
Expand All @@ -444,7 +444,7 @@ describe('Radio Browser', () => {
})

expect(scope.isDone()).toBe(true)
expect(result).toEqual([mockStation])
expect(result).toEqual([getMockStation()])
})

test('send station click', async () => {
Expand Down Expand Up @@ -541,7 +541,7 @@ describe('Radio Browser', () => {

const headerName = 'x-jest-test'
const headerValue = '1'
const mockResult = [mockStationResponse]
const mockResult = [getMockResponse()]
const query = {
taglist: 'rap,pop,jazz'
}
Expand Down Expand Up @@ -571,7 +571,7 @@ describe('Radio Browser', () => {
)

expect(scope.isDone()).toBe(true)
expect(result).toEqual([mockStation])
expect(result).toEqual([getMockStation()])
})
})
describe('Show or hide broken stations', () => {
Expand All @@ -582,7 +582,7 @@ describe('Radio Browser', () => {

const headerName = 'x-jest-test'
const headerValue = '1'
const mockResult = [mockStationResponse]
const mockResult = [getMockResponse()]
const query = {
taglist: 'rap,pop,jazz'
}
Expand Down Expand Up @@ -612,7 +612,7 @@ describe('Radio Browser', () => {
)

expect(scope.isDone()).toBe(true)
expect(result).toEqual([mockStation])
expect(result).toEqual([getMockStation()])
})
})

Expand All @@ -622,7 +622,7 @@ describe('Radio Browser', () => {

const headerName = 'x-jest-test'
const headerValue = '1'
const mockResult = [mockStationResponse]
const mockResult = [getMockResponse()]
const query = {
taglist: 'rap,pop,jazz',
hidebroken: 'true'
Expand Down Expand Up @@ -651,15 +651,15 @@ describe('Radio Browser', () => {
)

expect(scope.isDone()).toBe(true)
expect(result).toEqual([mockStation])
expect(result).toEqual([getMockStation()])
})
test('Show broken stations', async () => {
const userAgent = 'test'
const api = new RadioBrowserApi(globalTest.fetch, userAgent)

const headerName = 'x-jest-test'
const headerValue = '1'
const mockResult = [mockStationResponse]
const mockResult = [getMockResponse()]
const query = {
taglist: 'rap,pop,jazz',
hidebroken: 'false'
Expand Down Expand Up @@ -688,18 +688,14 @@ describe('Radio Browser', () => {
)

expect(scope.isDone()).toBe(true)
expect(result).toEqual([mockStation])
expect(result).toEqual([getMockStation()])
})
test('remove tags longer than 10 characters', async () => {
test('Remove stations with the same ids', async () => {
const userAgent = 'test'
const api = new RadioBrowserApi(globalTest.fetch, userAgent)

const response = Object.assign({}, mockStationResponse, {
tags: 'a,b,taglongerthan10characters'
})
const headerName = 'x-jest-test'
const headerValue = '1'
const mockResult = [response]
const mockResult = [getMockResponse(), getMockResponse()]
const tag = 'jazz'
const query = { order: 'name', reverse: true }

Expand Down Expand Up @@ -728,44 +724,40 @@ describe('Radio Browser', () => {
)

expect(scope.isDone()).toBe(true)
expect(result).toEqual([
Object.assign({}, mockStation, { tags: ['a', 'b'] })
])
expect(result).toEqual([getMockStation()])
})
test('Remove stations with the same ids', async () => {
const userAgent = 'test'
const api = new RadioBrowserApi(globalTest.fetch, userAgent)
const headerName = 'x-jest-test'
const headerValue = '1'
const mockResult = [mockStationResponse, mockStationResponse]

test('Remove duplicated tags', async () => {
const api = new RadioBrowserApi(globalTest.fetch)
const tag = 'jazz'
const query = { order: 'name', reverse: true }
const mockResponse = getMockResponse()
const duplicatedTags = mockResponse.tags.split(',')
duplicatedTags.push(duplicatedTags[0], duplicatedTags[0])
mockResponse.tags = duplicatedTags.toString()
const mockResult = [mockResponse]
const scope = nock(baseUrl)
.get(`/json/stations/bytag/${tag}`)
.reply(200, mockResult)

const scope = nock(baseUrl, {
reqheaders: {
[headerName]: headerValue,
'user-agent': userAgent
}
})
const result = await api.getStationsBy(StationSearchType.byTag, tag)

expect(scope.isDone()).toBe(true)
expect(result).toEqual([getMockStation()])
})

test('Remove tags over 10 characters long', async () => {
const api = new RadioBrowserApi(globalTest.fetch)
const tag = 'jazz'
const mockResponse = getMockResponse()
mockResponse.tags = mockResponse.tags.concat(',tag with over 10 characters')
const mockResult = [mockResponse]
const scope = nock(baseUrl)
.get(`/json/stations/bytag/${tag}`)
.query({
hidebroken: 'true',
...query
})
.reply(200, mockResult)

const result = await api.getStationsBy(
StationSearchType.byTag,
tag,
query as StationQuery,
{
headers: {
[headerName]: headerValue
}
}
)
const result = await api.getStationsBy(StationSearchType.byTag, tag)

expect(scope.isDone()).toBe(true)
expect(result).toEqual([mockStation])
expect(result).toEqual([getMockStation()])
})
})

0 comments on commit 16edcd2

Please sign in to comment.