Using jest for testing instead of doing it manually

This commit is contained in:
Zechariah 2022-02-05 13:13:15 +08:00
parent 75d85764c3
commit 2154dc67ab
11 changed files with 3574 additions and 336 deletions

6
babel.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
presets: [
["@babel/preset-env", { targets: { node: "current" } }],
"@babel/preset-typescript"
]
}

View File

@ -10,12 +10,21 @@
"type": "git", "type": "git",
"url": "https://github.com/zS1L3NT/ts-npm-ytmusic-api" "url": "https://github.com/zS1L3NT/ts-npm-ytmusic-api"
}, },
"scripts": {
"test": "jest"
},
"dependencies": { "dependencies": {
"axios": "^0.25.0", "axios": "^0.25.0",
"jest": "^27.4.7",
"tough-cookie": "^4.0.0" "tough-cookie": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"@babel/preset-typescript": "^7.16.7",
"@types/jest": "^27.4.0",
"@types/tough-cookie": "^4.0.1", "@types/tough-cookie": "^4.0.1",
"babel-jest": "^27.4.6",
"typescript": "^4.5.5", "typescript": "^4.5.5",
"validate-any": "^1.2.0" "validate-any": "^1.2.0"
}, },

View File

@ -70,7 +70,7 @@ export default class YTMusic {
} }
for (const cookieString of cookieStrings) { for (const cookieString of cookieStrings) {
const cookie = Cookie.parse(cookieString) const cookie = Cookie.parse(`${cookieString}`)
if (!cookie) return if (!cookie) return
this.cookiejar.setCookieSync(cookie, res.config.baseURL) this.cookiejar.setCookieSync(cookie, res.config.baseURL)

View File

@ -0,0 +1,337 @@
import ObjectValidator from "validate-any/dist/validators/ObjectValidator"
import Validator from "validate-any/dist/classes/Validator"
import YTMusic, {
AlbumBasic,
AlbumDetailed,
AlbumFull,
ArtistBasic,
ArtistDetailed,
ArtistFull,
PlaylistFull,
SongDetailed,
SongFull,
ThumbnailFull,
VideoDetailed,
VideoFull
} from ".."
import {
BOOLEAN,
iValidationError,
LIST,
NULL,
NUMBER,
OBJECT,
OR,
STRING,
validate
} from "validate-any"
//#region Interfaces
const THUMBNAIL_FULL: ObjectValidator<ThumbnailFull> = OBJECT({
url: STRING(),
width: NUMBER(),
height: NUMBER()
})
const ARTIST_BASIC: ObjectValidator<ArtistBasic> = OBJECT({
artistId: OR(STRING(), NULL()),
name: STRING()
})
const ALBUM_BASIC: ObjectValidator<AlbumBasic> = OBJECT({
albumId: STRING(),
name: STRING()
})
const SONG_DETAILED: ObjectValidator<SongDetailed> = OBJECT({
type: STRING("SONG"),
videoId: OR(STRING(), NULL()),
name: STRING(),
artists: LIST(ARTIST_BASIC),
album: ALBUM_BASIC,
duration: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL)
})
const VIDEO_DETAILED: ObjectValidator<VideoDetailed> = OBJECT({
type: STRING("VIDEO"),
videoId: OR(STRING(), NULL()),
name: STRING(),
artists: LIST(ARTIST_BASIC),
views: NUMBER(),
duration: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL)
})
const ARTIST_DETAILED: ObjectValidator<ArtistDetailed> = OBJECT({
artistId: STRING(),
name: STRING(),
type: STRING("ARTIST"),
thumbnails: LIST(THUMBNAIL_FULL)
})
const ALBUM_DETAILED: ObjectValidator<AlbumDetailed> = OBJECT({
type: STRING("ALBUM"),
albumId: STRING(),
playlistId: STRING(),
name: STRING(),
artists: LIST(ARTIST_BASIC),
year: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL)
})
const SONG_FULL: ObjectValidator<SongFull> = OBJECT({
type: STRING("SONG"),
videoId: OR(STRING(), NULL()),
name: STRING(),
artists: LIST(ARTIST_BASIC),
duration: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL),
description: STRING(),
formats: LIST(OBJECT()),
adaptiveFormats: LIST(OBJECT())
})
const VIDEO_FULL: ObjectValidator<VideoFull> = OBJECT({
type: STRING("VIDEO"),
videoId: OR(STRING(), NULL()),
name: STRING(),
artists: LIST(ARTIST_BASIC),
views: NUMBER(),
duration: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL),
description: STRING(),
unlisted: BOOLEAN(),
familySafe: BOOLEAN(),
paid: BOOLEAN(),
tags: LIST(STRING())
})
const ARTIST_FULL: ObjectValidator<ArtistFull> = OBJECT({
artistId: STRING(),
name: STRING(),
type: STRING("ARTIST"),
thumbnails: LIST(THUMBNAIL_FULL),
description: OR(STRING(), NULL()),
subscribers: NUMBER(),
topSongs: LIST(
OBJECT({
type: STRING("SONG"),
videoId: STRING(),
name: STRING(),
artists: LIST(ARTIST_BASIC),
album: ALBUM_BASIC,
thumbnails: LIST(THUMBNAIL_FULL)
})
),
topAlbums: LIST(ALBUM_DETAILED)
})
const ALBUM_FULL: ObjectValidator<AlbumFull> = OBJECT({
type: STRING("ALBUM"),
albumId: STRING(),
playlistId: STRING(),
name: STRING(),
artists: LIST(ARTIST_BASIC),
year: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL),
description: OR(STRING(), NULL()),
songs: LIST(SONG_DETAILED)
})
const PLAYLIST_DETAILED: ObjectValidator<PlaylistFull> = OBJECT({
type: STRING("PLAYLIST"),
playlistId: STRING(),
name: STRING(),
artist: ARTIST_BASIC,
videoCount: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL)
})
const PLAYLIST_VIDEO: ObjectValidator<Omit<VideoDetailed, "views">> = OBJECT({
type: STRING("VIDEO"),
videoId: OR(STRING(), NULL()),
name: STRING(),
artists: LIST(ARTIST_BASIC),
duration: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL)
})
//#endregion
const issues: iValidationError[][] = []
const queries = ["Lilac", "Weekend", "Eill", "Eminem", "Lisa Hannigan"]
const _expect = (data: any, validator: Validator<any>) => {
const { errors } = validate(data, validator)
if (errors.length > 0) {
issues.push(errors)
}
expect(errors.length).toBe(0)
}
const ytmusic = new YTMusic()
beforeAll(() => ytmusic.initialize())
queries.forEach(query => {
describe("Query: " + query, () => {
test("Search Songs", done => {
ytmusic
.search(query, "SONG")
.then(songs => {
_expect(songs, LIST(SONG_DETAILED))
done()
})
.catch(done)
})
test("Search Videos", done => {
ytmusic
.search(query, "VIDEO")
.then(videos => {
_expect(videos, LIST(VIDEO_DETAILED))
done()
})
.catch(done)
})
test("Search Artists", done => {
ytmusic
.search(query, "ARTIST")
.then(artists => {
_expect(artists, LIST(ARTIST_DETAILED))
done()
})
.catch(done)
})
test("Search Albums", done => {
ytmusic
.search(query, "ALBUM")
.then(albums => {
_expect(albums, LIST(ALBUM_DETAILED))
done()
})
.catch(done)
})
test("Search Playlists", done => {
ytmusic
.search(query, "PLAYLIST")
.then(playlists => {
_expect(playlists, LIST(PLAYLIST_DETAILED))
done()
})
.catch(done)
})
test("Search All", done => {
ytmusic
.search(query)
.then(results => {
_expect(
results,
LIST(
ALBUM_DETAILED,
ARTIST_DETAILED,
PLAYLIST_DETAILED,
SONG_DETAILED,
VIDEO_DETAILED
)
)
done()
})
.catch(done)
})
test("Get details of the first song result", done => {
ytmusic
.search(query, "SONG")
.then(songs => ytmusic.getSong(songs[0]!.videoId!))
.then(song => {
_expect(song, SONG_FULL)
done()
})
.catch(done)
})
test("Get details of the first video result", done => {
ytmusic
.search(query, "VIDEO")
.then(videos => ytmusic.getVideo(videos[0]!.videoId!))
.then(video => {
_expect(video, VIDEO_FULL)
done()
})
.catch(done)
})
test("Get details of the first artist result", done => {
ytmusic
.search(query, "ARTIST")
.then(artists => ytmusic.getArtist(artists[0]!.artistId!))
.then(artist => {
_expect(artist, ARTIST_FULL)
done()
})
.catch(done)
})
test("Get the songs of the first artist result", done => {
ytmusic
.search(query, "ARTIST")
.then(artists => ytmusic.getArtistSongs(artists[0]!.artistId!))
.then(songs => {
_expect(songs, LIST(SONG_DETAILED))
done()
})
.catch(done)
})
test("Get the albums of the first artist result", done => {
ytmusic
.search(query, "ARTIST")
.then(artists => ytmusic.getArtistAlbums(artists[0]!.artistId!))
.then(albums => {
_expect(albums, LIST(ALBUM_DETAILED))
done()
})
.catch(done)
})
test("Get details of the first album result", done => {
ytmusic
.search(query, "ALBUM")
.then(albums => ytmusic.getAlbum(albums[0]!.albumId!))
.then(album => {
_expect(album, ALBUM_FULL)
done()
})
.catch(done)
})
test("Get details of the first playlist result", done => {
ytmusic
.search(query, "PLAYLIST")
.then(playlists => ytmusic.getPlaylist(playlists[0]!.playlistId!))
.then(playlist => {
_expect(playlist, PLAYLIST_DETAILED)
done()
})
.catch(done)
})
test("Get the videos of the first playlist result", done => {
ytmusic
.search(query, "PLAYLIST")
.then(playlists => ytmusic.getPlaylistVideos(playlists[0]!.playlistId!))
.then(videos => {
_expect(videos, LIST(PLAYLIST_VIDEO))
done()
})
.catch(done)
})
})
})
afterAll(() => console.log("Issues:", issues))

View File

@ -1,81 +0,0 @@
import Validator from "validate-any/build/classes/Validator"
import YTMusic from "../YTMusic"
import {
ALBUM_DETAILED,
ALBUM_FULL,
ARTIST_DETAILED,
ARTIST_FULL,
PLAYLIST_DETAILED,
PLAYLIST_VIDEO,
SONG_DETAILED,
SONG_FULL,
VIDEO_DETAILED,
VIDEO_FULL
} from "./interfaces"
import { LIST, validate } from "validate-any"
const queries = ["Lilac", "Weekend", "Yours Raiden", "Eminem", "Lisa Hannigan"]
const ytmusic = new YTMusic()
ytmusic.initialize().then(() =>
queries.forEach(async query => {
const [songs, videos, artists, albums, playlists, results] = await Promise.all([
ytmusic.search(query, "SONG"),
ytmusic.search(query, "VIDEO"),
ytmusic.search(query, "ARTIST"),
ytmusic.search(query, "ALBUM"),
ytmusic.search(query, "PLAYLIST"),
ytmusic.search(query)
])
const [song, video, artist, artistSongs, artistAlbums, album, playlist, playlistVideos] =
await Promise.all([
ytmusic.getSong(songs[0].videoId!),
ytmusic.getVideo(videos[0].videoId!),
ytmusic.getArtist(artists[0].artistId),
ytmusic.getArtistSongs(artists[0].artistId),
ytmusic.getArtistAlbums(artists[0].artistId),
ytmusic.getAlbum(albums[0].albumId),
ytmusic.getPlaylist(playlists[0].playlistId),
ytmusic.getPlaylistVideos(playlists[0].playlistId)
])
const tests: [any, Validator<any>][] = [
[songs, LIST(SONG_DETAILED)],
[videos, LIST(VIDEO_DETAILED)],
[artists, LIST(ARTIST_DETAILED)],
[albums, LIST(ALBUM_DETAILED)],
[playlists, LIST(PLAYLIST_DETAILED)],
[
results,
LIST(
ALBUM_DETAILED,
ARTIST_DETAILED,
PLAYLIST_DETAILED,
SONG_DETAILED,
VIDEO_DETAILED
)
],
[song, SONG_FULL],
[video, VIDEO_FULL],
[artist, ARTIST_FULL],
[artistSongs, LIST(SONG_DETAILED)],
[artistAlbums, LIST(ALBUM_DETAILED)],
[album, ALBUM_FULL],
[playlist, PLAYLIST_DETAILED],
[playlistVideos, LIST(PLAYLIST_VIDEO)]
]
for (const [value, validator] of tests) {
const result = validate(value, validator)
if (!result.success) {
console.log(JSON.stringify(value))
console.log(validator.formatSchema())
console.log(result.errors)
process.exit(0)
}
}
console.log(`Valid 🎉 - ${query}`)
})
)

View File

@ -1,146 +0,0 @@
import ObjectValidator from "validate-any/build/validators/ObjectValidator"
import {
AlbumBasic,
AlbumDetailed,
AlbumFull,
ArtistBasic,
ArtistDetailed,
ArtistFull,
PlaylistFull,
SongDetailed,
SongFull,
ThumbnailFull,
VideoDetailed,
VideoFull
} from "../index"
import { BOOLEAN, LIST, NULL, NUMBER, OBJECT, OR, STRING } from "validate-any"
export const THUMBNAIL_FULL: ObjectValidator<ThumbnailFull> = OBJECT({
url: STRING(),
width: NUMBER(),
height: NUMBER()
})
export const ARTIST_BASIC: ObjectValidator<ArtistBasic> = OBJECT({
artistId: OR(STRING(), NULL()),
name: STRING()
})
export const ALBUM_BASIC: ObjectValidator<AlbumBasic> = OBJECT({
albumId: STRING(),
name: STRING()
})
export const SONG_DETAILED: ObjectValidator<SongDetailed> = OBJECT({
type: STRING("SONG"),
videoId: OR(STRING(), NULL()),
name: STRING(),
artists: LIST(ARTIST_BASIC),
album: ALBUM_BASIC,
duration: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL)
})
export const VIDEO_DETAILED: ObjectValidator<VideoDetailed> = OBJECT({
type: STRING("VIDEO"),
videoId: OR(STRING(), NULL()),
name: STRING(),
artists: LIST(ARTIST_BASIC),
views: NUMBER(),
duration: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL)
})
export const ARTIST_DETAILED: ObjectValidator<ArtistDetailed> = OBJECT({
artistId: STRING(),
name: STRING(),
type: STRING("ARTIST"),
thumbnails: LIST(THUMBNAIL_FULL)
})
export const ALBUM_DETAILED: ObjectValidator<AlbumDetailed> = OBJECT({
type: STRING("ALBUM"),
albumId: STRING(),
playlistId: STRING(),
name: STRING(),
artists: LIST(ARTIST_BASIC),
year: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL)
})
export const SONG_FULL: ObjectValidator<SongFull> = OBJECT({
type: STRING("SONG"),
videoId: OR(STRING(), NULL()),
name: STRING(),
artists: LIST(ARTIST_BASIC),
duration: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL),
description: STRING(),
formats: LIST(OBJECT()),
adaptiveFormats: LIST(OBJECT())
})
export const VIDEO_FULL: ObjectValidator<VideoFull> = OBJECT({
type: STRING("VIDEO"),
videoId: OR(STRING(), NULL()),
name: STRING(),
artists: LIST(ARTIST_BASIC),
views: NUMBER(),
duration: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL),
description: STRING(),
unlisted: BOOLEAN(),
familySafe: BOOLEAN(),
paid: BOOLEAN(),
tags: LIST(STRING())
})
export const ARTIST_FULL: ObjectValidator<ArtistFull> = OBJECT({
artistId: STRING(),
name: STRING(),
type: STRING("ARTIST"),
thumbnails: LIST(THUMBNAIL_FULL),
description: OR(STRING(), NULL()),
subscribers: NUMBER(),
topSongs: LIST(
OBJECT({
type: STRING("SONG"),
videoId: STRING(),
name: STRING(),
artists: LIST(ARTIST_BASIC),
album: ALBUM_BASIC,
thumbnails: LIST(THUMBNAIL_FULL)
})
),
topAlbums: LIST(ALBUM_DETAILED)
})
export const ALBUM_FULL: ObjectValidator<AlbumFull> = OBJECT({
type: STRING("ALBUM"),
albumId: STRING(),
playlistId: STRING(),
name: STRING(),
artists: LIST(ARTIST_BASIC),
year: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL),
description: OR(STRING(), NULL()),
songs: LIST(SONG_DETAILED)
})
export const PLAYLIST_DETAILED: ObjectValidator<PlaylistFull> = OBJECT({
type: STRING("PLAYLIST"),
playlistId: STRING(),
name: STRING(),
artist: ARTIST_BASIC,
videoCount: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL)
})
export const PLAYLIST_VIDEO: ObjectValidator<Omit<VideoDetailed, "views">> = OBJECT({
type: STRING("VIDEO"),
videoId: OR(STRING(), NULL()),
name: STRING(),
artists: LIST(ARTIST_BASIC),
duration: NUMBER(),
thumbnails: LIST(THUMBNAIL_FULL)
})

View File

@ -1,10 +0,0 @@
import YTMusic from "../YTMusic"
const ytmusic = new YTMusic()
ytmusic.initialize().then(() => {
ytmusic.search("Lilac", "SONG").then(res => {
ytmusic.getSong(res.find(r => !!r.videoId)!.videoId!).then(res => {
console.log(res)
})
})
})

View File

@ -1,35 +0,0 @@
const traverse = (data: any, keys: string, single = false) => {
const again = (data: any, key: string): any => {
var res = []
data.hasOwnProperty(key) && res.push(data[key])
if (single && data.hasOwnProperty(key)) {
return res.shift()
}
if (data instanceof Array) {
for (let i = 0; i < data.length; i++) {
res = res.concat(again(data[i], key))
}
} else if (data instanceof Object) {
const c = Object.keys(data)
if (c.length > 0) {
for (let i = 0; i < c.length; i++) {
res = res.concat(again(data[c[i]], key))
}
}
}
return res.length == 1 ? res.shift() : res
}
let z = keys.split(":"),
value = data
for (let i = 0; i < z.length; i++) {
value = again(value, z[i])
}
console.log(value)
}
traverse(require("./data.json"), "playNavigationEndpoint:videoId")
export default {}

View File

@ -1,32 +0,0 @@
const traverse = (data: any, keys: string[], single: boolean = false) => {
const again = (data: any, key: string): any => {
let res = []
if (data instanceof Object && key in data) {
res.push(data[key])
}
if (data instanceof Array) {
res.push(...data.map(v => again(v, key)).flat())
} else if (data instanceof Object) {
res.push(
...Object.keys(data)
.map(k => again(data[k], key))
.flat()
)
}
return res.length === 1 ? res[0] : res
}
let value = data
for (const key of keys) {
value = again(value, key)
}
return value
}
traverse(require("./data.json")[0], ["playNavigationEndpoint", "videoId"], true)
export default {}

View File

@ -1,30 +0,0 @@
const traverse = (data: any, ...keys: string[]) => {
const again = (data: any, key: string): any => {
let res = []
if (data instanceof Object && key in data) {
res.push(data[key])
}
if (data instanceof Array) {
res.push(...data.map(v => again(v, key)).flat())
} else if (data instanceof Object) {
res.push(
...Object.keys(data)
.map(k => again(data[k], key))
.flat()
)
}
return res.length === 1 ? res[0] : res
}
let value = data
for (const key of keys) {
value = again(value, key)
}
return value
}
export default traverse

3222
yarn.lock

File diff suppressed because it is too large Load Diff