Skip to content

Commit

Permalink
fix: Handle private videos in playlists to prevent UNKNOWN_ERROR in Y…
Browse files Browse the repository at this point in the history
…outubeAdapter#getFullPlaylist and refactoring (#20)

* test: Add tests for utils

* chore: Release 1.1.3 🚀

* test: Add tests for result of lib

* chore: Update some scripts for test

* refactor: Extract entity conversion functions from YouTubeAdapter class

- Moved entity conversion functions to a separate module for better code organization and maintainability.
- Updated YouTubeAdapter class to use the new conversion module.

* test: Add test for EntityConverter

* refactor: Simplify entity conversion functions to handle single items

* fix: Handle private videos in playlists to prevent UNKNOWN_ERROR in YoutubeAdapter#getFullPlaylist

- Closes: #3
- Updated YoutubeAdapter#getFullPlaylist to properly handle private videos in playlists.
- Added checks to skip private videos and continue processing the rest of the playlist.
- Improved error handling to avoid throwing UNKNOWN_ERROR when private videos are encountered.

* chore: Release 1.1.4 🚀
  • Loading branch information
suzuki3jp authored Dec 30, 2024
1 parent cc1ff3e commit fa3f7a1
Show file tree
Hide file tree
Showing 7 changed files with 451 additions and 115 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
with:
devDeps: true
- name: Run Vitest
run: npx vitest --coverage.enabled true
run: pnpm run test:coverage
- name: Report coverage
uses: davelosert/vitest-coverage-report-action@v2

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "pmfy",
"version": "1.1.3",
"version": "1.1.4",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "pnpm vitest run --coverage.enabled true"
"test": "pnpm vitest run",
"test:coverage": "pnpm vitest run --coverage.enabled true"
},
"dependencies": {
"@emotion/cache": "^11.14.0",
Expand Down
82 changes: 82 additions & 0 deletions src/lib/result/result.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { describe, expect, test } from "vitest";

import { Failure, Result, Success } from ".";

const data = [
"",
"data",
{},
{ key: "value" },
0,
1,
[],
[""],
[9],
[{}],
[{ key: "value" }],
undefined,
null,
];
const changedData = [
"",
"changed data",
{},
{ key: "changed value" },
0,
1,
[],
[""],
[9],
[{}],
[{ key: "changed value" }],
undefined,
null,
];

describe("Result lib", () => {
describe("Success", () => {
test("should return true when isSuccess is called", () => {
const success = new Success("success");
expect(success.isSuccess()).toBe(true);
});
test("should return false when isFailure is called", () => {
const success = new Success("success");
expect(success.isFailure()).toBe(false);
});
test("should return the data from the constructor when referenced the data property", () => {
for (const d of data) {
const success = new Success(d);
expect(success.data).toBe(d);
}
});
test("should return the data from the setData method", () => {
for (let i = 0; i < data.length; i++) {
const success = new Success(data[i]);
expect(success.setData(changedData[i])).toBe(changedData[i]);
}
});
});

describe("Failure", () => {
test("should return false when isSuccess is called", () => {
const failure = new Failure("failure");
expect(failure.isSuccess()).toBe(false);
});
test("should return true when isFailure is called", () => {
const failure = new Failure("failure");
expect(failure.isFailure()).toBe(true);
});
test("should return the data from the constructor when referenced the data property", () => {
for (const d of data) {
const failure = new Failure(d);
expect(failure.data).toBe(d);
}
});
test("should return the data from the setData method", () => {
for (let i = 0; i < data.length; i++) {
const failure = new Failure(data[i]);
expect(failure.setData(changedData[i])).toBe(changedData[i]);
}
});
});
});
235 changes: 235 additions & 0 deletions src/lib/youtube-adapter/EntityConverter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import { describe, expect, test } from "vitest";

import { Playlist, PlaylistItem } from "@/lib/base-adapter";
import type { youtube_v3 } from "googleapis";
import {
convertToPlaylist,
convertToPlaylistItem,
getThumbnailUrlFromAPIData,
} from "./EntityConverter";

// The test does not verify the logic for selecting the thumbnail URL from the API response.
// The logic is tested in the `getThumbnailUrlFromAPIData` function test.
describe("convertToPlaylist", () => {
test("should convert the given API response to a Playlist instance", () => {
const data: [youtube_v3.Schema$Playlist, Playlist][] = [
[
{
id: "foo-id",
snippet: {
title: "foo-title",
thumbnails: { default: { url: "foo-default-url" } },
},
},
new Playlist({
id: "foo-id",
title: "foo-title",
thumbnailUrl: "foo-default-url",
}),
],
[
{
id: "foo-id",
snippet: {
title: "foo-title",
thumbnails: {
default: { url: "foo-default-url" },
high: {
url: "foo-high-url",
},
},
},
},
new Playlist({
id: "foo-id",
title: "foo-title",
thumbnailUrl: "foo-high-url",
}),
],
[
{
id: "foo-id",
snippet: {
title: "foo-title",
thumbnails: {
maxres: { url: "foo-maxres-url" },
},
},
},
new Playlist({
id: "foo-id",
title: "foo-title",
thumbnailUrl: "foo-maxres-url",
}),
],
];

for (const [apiData, expected] of data) {
expect(convertToPlaylist(apiData)).toStrictEqual(expected);
}
});
});

describe("convertToPlaylistItem", () => {
test("should convert the given API response to a PlaylistItem instance", () => {
const data: [youtube_v3.Schema$PlaylistItem, PlaylistItem | null][] = [
[
{
id: "foo-id",
snippet: {
title: "foo-title",
position: 1,
videoOwnerChannelTitle: "foo-channel-title",
resourceId: { videoId: "foo-video-id" },
thumbnails: { default: { url: "foo-default-url" } },
},
},
new PlaylistItem({
id: "foo-id",
title: "foo-title",
thumbnailUrl: "foo-default-url",
position: 1,
videoId: "foo-video-id",
author: "foo-channel-title",
}),
],
[
{
id: "foo-id",
snippet: {
title: "foo-title",
position: 1,
videoOwnerChannelTitle: "foo-channel-title",
resourceId: { videoId: "foo-video-id" },
thumbnails: {
default: { url: "foo-default-url" },
high: { url: "foo-high-url" },
},
},
},
new PlaylistItem({
id: "foo-id",
title: "foo-title",
thumbnailUrl: "foo-high-url",
position: 1,
videoId: "foo-video-id",
author: "foo-channel-title",
}),
],
[
{
id: "foo-id",
snippet: {
title: "foo-title",
position: 1,
videoOwnerChannelTitle: "foo-channel-title",
resourceId: { videoId: "foo-video-id" },
thumbnails: {
maxres: { url: "foo-maxres-url" },
},
},
},
new PlaylistItem({
id: "foo-id",
title: "foo-title",
thumbnailUrl: "foo-maxres-url",
position: 1,
videoId: "foo-video-id",
author: "foo-channel-title",
}),
],
[
{
id: "foo-id",
snippet: {
title: "foo-title",
position: 1,
videoOwnerChannelTitle: "foo-channel-title - Topic",
resourceId: { videoId: "foo-video-id" },
thumbnails: {
default: { url: "foo-default-url" },
},
},
},
new PlaylistItem({
id: "foo-id",
title: "foo-title",
thumbnailUrl: "foo-default-url",
position: 1,
videoId: "foo-video-id",
author: "foo-channel-title",
}),
],
[
{
kind: "youtube#playlistItem",
etag: "XCet38iaoNCg_iYBc1N8sAKQb50",
id: "UExWTERkNGRESGE3TXVmT2JfMks5YUpHa1FRSjctb3p5Ry4zRjM0MkVCRTg0MkYyQTM0",
snippet: {
publishedAt: "2021-10-30T13:20:19Z",
channelId: "UCQn3V_Cuwq-HVtzpqMIYjHQ",
title: "Private video",
description: "This video is private.",
thumbnails: {},
channelTitle: "鈴木",
playlistId: "PLVLDd4dDHa7MufOb_2K9aJGkQQJ7-ozyG",
position: 19,
resourceId: {
kind: "youtube#video",
videoId: "WSVTrNkWOoU",
},
},
contentDetails: { videoId: "WSVTrNkWOoU" },
status: { privacyStatus: "private" },
},
null,
],
];

for (const [apiData, expected] of data) {
expect(convertToPlaylistItem(apiData)).toStrictEqual(expected);
}
});
});

describe("getThumbnailUrlFromAPIData", () => {
test("should return the URL of the highest resolution thumbnail", () => {
const data: [youtube_v3.Schema$ThumbnailDetails, string | undefined][] =
[
[{}, undefined],
[{ default: { url: "foo-default-url" } }, "foo-default-url"],
[
{
default: { url: "foo-default-url" },
medium: { url: "foo-medium-url" },
},
"foo-medium-url",
],
[
{
default: { url: "foo-default-url" },
high: { url: "foo-high-url" },
},
"foo-high-url",
],
[
{
default: { url: "foo-default-url" },
standard: { url: "foo-standard-url" },
},
"foo-standard-url",
],
[
{
default: { url: "foo-default-url" },
maxres: { url: "foo-maxres-url" },
},
"foo-maxres-url",
],
];

for (const [apiData, expected] of data) {
expect(getThumbnailUrlFromAPIData(apiData)).toBe(expected);
}
});
});
Loading

0 comments on commit fa3f7a1

Please sign in to comment.