Skip to content

Commit

Permalink
feat: parse tags and language properties in to arrays
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Signature of the station response has been changed
  • Loading branch information
ivandotv committed Nov 3, 2020
1 parent c480a2b commit c6c6f95
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 25 deletions.
4 changes: 2 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ 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
codec: string
Expand Down
41 changes: 37 additions & 4 deletions src/radioBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
StationQuery
} 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 @@ -111,7 +114,7 @@ export class RadioBrowserApi {
config?: Query,
fetchConfig?: RequestInit
): Promise<CountryResult[]> {
tag = tag ? tag.toLowerCase() : ''
tag = tag ? tag.toLowerCase() : '' // empty string returns all tags

return this.runRequest(this.buildRequest('tags', tag, config), fetchConfig)
}
Expand All @@ -130,30 +133,60 @@ export class RadioBrowserApi {

search = search ? search.toLowerCase() : ''

return this.runRequest(
const stations = await this.runRequest<StationResponse[]>(
this.buildRequest(`stations/${searchType.toLowerCase()}`, search, query),
fetchConfig
)

return this.normalizeStations(stations)
}

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(',')

result.push(station)
}

return result
}

async getAllStations(
query?: Omit<StationQuery, 'hideBroken'>,
fetchConfig?: RequestInit
): Promise<Station[]> {
return this.runRequest(
const stations = await this.runRequest<StationResponse[]>(
this.buildRequest('stations', '', query),
fetchConfig
)

return this.normalizeStations(stations)
}

async searchStations(
query: AdvancedStationQuery,
fetchConfig?: RequestInit
): Promise<Station[]> {
return this.runRequest(
const stations = await this.runRequest<StationResponse[]>(
this.buildRequest('stations/search', undefined, query),
fetchConfig
)

return this.normalizeStations(stations)
}

async sendStationClick(
Expand Down
113 changes: 95 additions & 18 deletions tests/radioBrowser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +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'

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

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

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

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

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

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

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

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

const headerName = 'x-jest-test'
const headerValue = '1'
const mockResult = [{ name: 'rs', stationcount: 10 }]
const mockResult = [mockStationResponse]
const query = { order: 'name', reverse: true }

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

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

test('send station click', async () => {
Expand All @@ -452,7 +453,6 @@ describe('Radio Browser', () => {

const headerName = 'x-jest-test'
const headerValue = '1'
const mockResult = [{ name: 'rs', stationcount: 10 }]
const stationUuid = '1234567890'

const scope = nock(baseUrl, {
Expand All @@ -462,16 +462,15 @@ describe('Radio Browser', () => {
}
})
.get(`/json/url/${stationUuid}`)
.reply(200, mockResult)
.reply(200, {})

const result = await api.sendStationClick(stationUuid, {
await api.sendStationClick(stationUuid, {
headers: {
[headerName]: headerValue
}
})

expect(scope.isDone()).toBe(true)
expect(result).toEqual(mockResult)
})

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

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

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

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

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

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

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

expect(scope.isDone()).toBe(true)
expect(result).toEqual(mockResult)
expect(result).toEqual([mockStation])
})
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 = [{ name: 'rs', stationcount: 10 }]
const mockResult = [mockStationResponse]
const query = {
taglist: 'rap,pop,jazz',
hidebroken: 'false'
Expand Down Expand Up @@ -689,6 +688,84 @@ describe('Radio Browser', () => {
)

expect(scope.isDone()).toBe(true)
expect(result).toEqual(mockResult)
expect(result).toEqual([mockStation])
})
test('remove tags longer than 10 characters', 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 tag = 'jazz'
const query = { order: 'name', reverse: true }

const scope = nock(baseUrl, {
reqheaders: {
[headerName]: headerValue,
'user-agent': userAgent
}
})
.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
}
}
)

expect(scope.isDone()).toBe(true)
expect(result).toEqual([
Object.assign({}, mockStation, { tags: ['a', 'b'] })
])
})
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]
const tag = 'jazz'
const query = { order: 'name', reverse: true }

const scope = nock(baseUrl, {
reqheaders: {
[headerName]: headerValue,
'user-agent': userAgent
}
})
.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
}
}
)

expect(scope.isDone()).toBe(true)
expect(result).toEqual([mockStation])
})
})
53 changes: 53 additions & 0 deletions tests/utils/mockStation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
export const mockStationResponse = {
changeuuid: '9619b77b-0601-11e8-ae97-52543be04c81',
stationuuid: '9619b778-0601-11e8-ae97-52543be04c81',
name: 'Radio 021',
url: 'http://109.206.96.106:8000/',
url_resolved: 'http://109.206.96.106:8000/',
homepage: 'http://www.021.rs/',
favicon: 'http://www.021.rs/favicon.ico',
tags: 'talk,music,news',
country: 'Serbia',
countrycode: 'RS',
state: 'Vojvodina',
language: 'serbian,english,german',
votes: 44,
lastchangetime: '2020-01-19 13:17:10',
codec: 'MP3',
bitrate: 128,
hls: 0,
lastcheckok: 1,
lastchecktime: '2020-10-27 06:04:51',
lastcheckoktime: '2020-10-27 06:04:51',
lastlocalchecktime: '2020-10-27 06:04:51',
clicktimestamp: '2020-10-25 18:33:05',
clickcount: 14,
clicktrend: -1
}

export const mockStation = {
changeuuid: '9619b77b-0601-11e8-ae97-52543be04c81',
stationuuid: '9619b778-0601-11e8-ae97-52543be04c81',
name: 'Radio 021',
url: 'http://109.206.96.106:8000/',
url_resolved: 'http://109.206.96.106:8000/',
homepage: 'http://www.021.rs/',
favicon: 'http://www.021.rs/favicon.ico',
tags: ['talk', 'music', 'news'],
country: 'Serbia',
countrycode: 'RS',
state: 'Vojvodina',
language: ['serbian', 'english', 'german'],
votes: 44,
lastchangetime: '2020-01-19 13:17:10',
codec: 'MP3',
bitrate: 128,
hls: 0,
lastcheckok: 1,
lastchecktime: '2020-10-27 06:04:51',
lastcheckoktime: '2020-10-27 06:04:51',
lastlocalchecktime: '2020-10-27 06:04:51',
clicktimestamp: '2020-10-25 18:33:05',
clickcount: 14,
clicktrend: -1
}
1 change: 0 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"lib": ["DOM"],
"strict": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"isolatedModules": true
}
Expand Down

0 comments on commit c6c6f95

Please sign in to comment.