UNPKG

@book000/pixivts

Version:

pixiv Unofficial API Library for TypeScript

729 lines 38.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /* eslint-disable @typescript-eslint/unbound-method */ const options_1 = require("./options"); const pixiv_1 = __importDefault(require("./pixiv")); const detail_1 = require("./types/endpoints/v1/illust/detail"); const ranking_1 = require("./types/endpoints/v1/illust/ranking"); const recommended_1 = require("./types/endpoints/v1/illust/recommended"); const series_1 = require("./types/endpoints/v1/illust/series"); const recommended_2 = require("./types/endpoints/v1/manga/recommended"); const ranking_2 = require("./types/endpoints/v1/novel/ranking"); const recommended_3 = require("./types/endpoints/v1/novel/recommended"); const related_1 = require("./types/endpoints/v1/novel/related"); const illust_1 = require("./types/endpoints/v1/search/illust"); const novel_1 = require("./types/endpoints/v1/search/novel"); const illust_2 = require("./types/endpoints/v1/user/bookmarks/illust"); const novel_2 = require("./types/endpoints/v1/user/bookmarks/novel"); const detail_2 = require("./types/endpoints/v1/user/detail"); const related_2 = require("./types/endpoints/v2/illust/related"); const detail_3 = require("./types/endpoints/v2/novel/detail"); const series_2 = require("./types/endpoints/v2/novel/series"); const utils_1 = require("./utils"); const axios_1 = __importDefault(require("axios")); const node_fs_1 = __importDefault(require("node:fs")); jest.setTimeout(120000); // 120sec describe('pixiv', () => { let pixiv; beforeAll(async () => { // .envファイルから環境変数を読み込む。dotenvなどを使用しない if (node_fs_1.default.existsSync('.env')) { const env = node_fs_1.default.readFileSync('.env', 'utf8'); for (const line of env.split('\n')) { const [key, value] = line.split('='); if (key && value) { process.env[key] = value; } } } const pixivRefreshToken = process.env.PIXIV_REFRESH_TOKEN; if (!pixivRefreshToken) { throw new Error('PIXIV_REFRESH_TOKEN is not set'); } pixiv = await pixiv_1.default.of(pixivRefreshToken, { debugOptions: { outputResponse: { enable: true, }, }, }); }); afterAll(async () => { await pixiv.close(); }); it('illustDetail:107565629[illust]', async () => { const illustDetail = await pixiv.illustDetail({ illustId: 107565629, }); expect(illustDetail.status).toBe(200); expect(illustDetail.data.illust.id).toBe(107565629); expect(illustDetail.data.illust.title).toBe('フブキちゃん衣装着てみたみょ~ん余'); expect(illustDetail.data.illust.type).toBe('illust'); // "square_medium": "https://i.pximg.net/c/360x360_70/img-master/img/2023/04/27/13/39/51/107565629_p0_square1200.jpg", expect(illustDetail.data.illust.image_urls.square_medium).toMatch(/^https:\/\/i\.pximg\.net\/c\/360x360_70\/img-master\/img\/.+_square1200\.jpg$/); // "medium": "https://i.pximg.net/c/540x540_70/img-master/img/2023/04/27/13/39/51/107565629_p0_master1200.jpg", expect(illustDetail.data.illust.image_urls.medium).toMatch(/^https:\/\/i\.pximg\.net\/c\/540x540_70\/img-master\/img\/.+_master1200\.jpg$/); // "large": "https://i.pximg.net/c/600x1200_90/img-master/img/2023/04/27/13/39/51/107565629_p0_master1200.jpg", expect(illustDetail.data.illust.image_urls.large).toMatch(/^https:\/\/i\.pximg\.net\/c\/600x1200_90\/img-master\/img\/.+_master1200\.jpg$/); expect(illustDetail.data.illust.caption).toBe(''); expect(illustDetail.data.illust.restrict).toBe(0); expect(illustDetail.data.illust.user.id).toBe(16668308); expect(illustDetail.data.illust.user.name).toBe('桜もち'); expect(illustDetail.data.illust.user.account).toBe('yukitohoshizora'); // https://i.pximg.net/user-profile/img/ expect(illustDetail.data.illust.user.profile_image_urls.medium).toMatch(/^https:\/\/i\.pximg\.net\/user-profile\/img\/.+\.png$/); expect(illustDetail.data.illust.tags.length).toBeGreaterThan(0); expect(illustDetail.data.illust.tools).toBeDefined(); expect(illustDetail.data.illust.tools.length).toBe(0); expect(illustDetail.data.illust.create_date).toBe('2023-04-27T13:39:51+09:00'); expect(illustDetail.data.illust.page_count).toBe(1); expect(illustDetail.data.illust.width).toBe(1728); expect(illustDetail.data.illust.height).toBe(2626); expect(illustDetail.data.illust.sanity_level).toBe(2); expect(illustDetail.data.illust.x_restrict).toBe(0); expect(illustDetail.data.illust.series).toBeNull(); // "original_image_url": "https://i.pximg.net/img-original/img/2023/04/27/13/39/51/107565629_p0.png", expect(illustDetail.data.illust.meta_single_page.original_image_url).toMatch(/^https:\/\/i\.pximg\.net\/img-original\/img\/.+_p0\.png$/); expect(illustDetail.data.illust.meta_pages).toHaveLength(0); expect(illustDetail.data.illust.total_bookmarks).toBeGreaterThan(0); expect(illustDetail.data.illust.total_view).toBeGreaterThan(0); expect(illustDetail.data.illust.visible).toBe(true); expect(illustDetail.data.illust.illust_ai_type).toBe(1); expect(illustDetail.data.illust.illust_book_style).toBe(0); const check = new detail_1.GetV1IllustDetailCheck(); expect(() => check.throwIfResponseFailed(illustDetail.data)).not.toThrow(); expect((0, utils_1.omit)(illustDetail.data.illust, [ 'total_bookmarks', 'total_comments', 'total_view', ])).toMatchSnapshot(); }); it('illustDetail:103905962[manga]', async () => { const illustDetail = await pixiv.illustDetail({ illustId: 103905962, }); expect(illustDetail.status).toBe(200); expect(illustDetail.data.illust.id).toBe(103905962); expect(illustDetail.data.illust.title).toBe('【C101】あくあに恋した猫の話'); expect(illustDetail.data.illust.type).toBe('manga'); // "square_medium": "https://i.pximg.net/c/360x360_70/img-master/img/2023/04/27/13/39/51/107565629_p0_square1200.jpg", expect(illustDetail.data.illust.image_urls.square_medium).toMatch(/^https:\/\/i\.pximg\.net\/c\/360x360_70\/img-master\/img\/.+_square1200\.jpg$/); // "medium": "https://i.pximg.net/c/540x540_70/img-master/img/2023/04/27/13/39/51/107565629_p0_master1200.jpg", expect(illustDetail.data.illust.image_urls.medium).toMatch(/^https:\/\/i\.pximg\.net\/c\/540x540_70\/img-master\/img\/.+_master1200\.jpg$/); // "large": "https://i.pximg.net/c/600x1200_90/img-master/img/2023/04/27/13/39/51/107565629_p0_master1200.jpg", expect(illustDetail.data.illust.image_urls.large).toMatch(/^https:\/\/i\.pximg\.net\/c\/600x1200_90\/img-master\/img\/.+_master1200\.jpg$/); expect(illustDetail.data.illust.caption).toBe("C101の新刊サンプルです!<br /><br />恋愛アドベンチャーゲーム『あくありうむ。』に登場する「猫」。<br />彼はどんなことを考えていたのか?<br />彼にとって、これはどんな物語だったのか?<br />そういった本編で語られなかった部分を想像で掘り下げたお話です。<br />※本文は『あくありうむ。』のネタバレおよび多少の負傷描写を含みます。苦手な方はご注意ください。<br /><br />【1日目東C-13b/くらうでぃ】でお待ちしています!<br /><br />メロンブックス様にて新刊の事前予約も始まっています!<br />気になる方は是非チェックしてみてください!<br />【<a href=\"https://www.melonbooks.co.jp/detail/detail.php?product_id=1746005\" target='_blank' rel='noopener noreferrer'>https://www.melonbooks.co.jp/detail/detail.php?product_id=1746005</a>】"); expect(illustDetail.data.illust.restrict).toBe(0); expect(illustDetail.data.illust.user.id).toBe(8166267); expect(illustDetail.data.illust.user.name).toBe('くらうど'); expect(illustDetail.data.illust.user.account).toBe('borinn0812'); // https://i.pximg.net/user-profile/img/ expect(illustDetail.data.illust.user.profile_image_urls.medium).toMatch(/^https:\/\/i\.pximg\.net\/user-profile\/img\/.+\.(?:jpg|png)$/); expect(illustDetail.data.illust.tags.length).toBeGreaterThan(0); expect(illustDetail.data.illust.tools).toBeDefined(); expect(illustDetail.data.illust.tools.length).toBeGreaterThan(0); expect(illustDetail.data.illust.create_date).toBe('2022-12-25T18:31:02+09:00'); expect(illustDetail.data.illust.page_count).toBe(8); expect(illustDetail.data.illust.width).toBe(2508); expect(illustDetail.data.illust.height).toBe(3541); expect(illustDetail.data.illust.sanity_level).toBe(2); expect(illustDetail.data.illust.x_restrict).toBe(0); expect(illustDetail.data.illust.series).toBeNull(); expect(illustDetail.data.illust.meta_single_page).toStrictEqual({}); expect(illustDetail.data.illust.meta_pages).toHaveLength(8); expect(illustDetail.data.illust.total_bookmarks).toBeGreaterThan(0); expect(illustDetail.data.illust.total_view).toBeGreaterThan(0); expect(illustDetail.data.illust.visible).toBe(true); expect(illustDetail.data.illust.illust_ai_type).toBe(1); expect(illustDetail.data.illust.illust_book_style).toBe(0); const check = new detail_1.GetV1IllustDetailCheck(); expect(() => check.throwIfResponseFailed(illustDetail.data)).not.toThrow(); expect((0, utils_1.omit)(illustDetail.data.illust, [ 'total_bookmarks', 'total_comments', 'total_view', ])).toMatchSnapshot(); }); it('illustRelated:107565629[illust]', async () => { const illustRelated = await pixiv.illustRelated({ illustId: 107565629, }); expect(illustRelated.status).toBe(200); expect(illustRelated.data.illusts).toBeDefined(); expect(illustRelated.data.illusts.length).toBeGreaterThan(0); const check = new related_2.GetV2IllustRelatedCheck(); expect(() => check.throwIfResponseFailed(illustRelated.data)).not.toThrow(); }); it('ugoiraDetail:83638393[ugoira]', async () => { const ugoiraDetail = await pixiv.ugoiraMetadata({ illustId: 83638393, }); expect(ugoiraDetail.status).toBe(200); // "medium": "https://i.pximg.net/img-zip-ugoira/img/2014/06/28/12/42/39/44360221_ugoira600x600.zip". expect(ugoiraDetail.data.ugoira_metadata.zip_urls.medium).toMatch(/^https:\/\/i\.pximg\.net\/img-zip-ugoira\/img\/.+_ugoira600x600\.zip$/); expect(ugoiraDetail.data.ugoira_metadata.frames).toHaveLength(96); }); it('searchIllust', async () => { const searchIllust = await pixiv.searchIllust({ word: 'ホロライブ', }); expect(searchIllust.status).toBe(200); expect(searchIllust.data).toBeDefined(); expect(searchIllust.data.illusts).toBeDefined(); expect(searchIllust.data.illusts.length).toBeGreaterThan(0); const check = new illust_1.GetV1SearchIllustCheck(); expect(() => check.throwIfResponseFailed(searchIllust.data)).not.toThrow(); }); it('illustRanking', async () => { const illustRanking = await pixiv.illustRanking(); expect(illustRanking.status).toBe(200); expect(illustRanking.data).toBeDefined(); expect(illustRanking.data.illusts).toBeDefined(); expect(illustRanking.data.illusts.length).toBeGreaterThan(0); const check = new ranking_1.GetV1IllustRankingCheck(); expect(() => check.throwIfResponseFailed(illustRanking.data)).not.toThrow(); }); it('illustRecommended', async () => { const recommendedIllust = await pixiv.illustRecommended(); expect(recommendedIllust.status).toBe(200); expect(recommendedIllust.data).toBeDefined(); expect(recommendedIllust.data.illusts).toBeDefined(); expect(recommendedIllust.data.illusts.length).toBeGreaterThan(0); const check = new recommended_1.GetV1IllustRecommendedCheck(); expect(() => check.throwIfResponseFailed(recommendedIllust.data)).not.toThrow(); }); it('illustSeries', async () => { const illustSeries = await pixiv.illustSeries({ illustSeriesId: 147483, }); expect(illustSeries.status).toBe(200); expect(illustSeries.data).toBeDefined(); expect(illustSeries.data.illust_series_detail.title).toBe('ホロライブ学パロ'); expect(illustSeries.data.illusts).toBeDefined(); expect(illustSeries.data.illusts.length).toBeGreaterThan(0); const check = new series_1.GetV1IllustSeriesCheck(); expect(() => check.throwIfResponseFailed(illustSeries.data)).not.toThrow(); expect((0, utils_1.omit)(illustSeries.data.illust_series_detail, ['series_work_count'])).toMatchSnapshot('illust_series_detail'); expect((0, utils_1.omit)(illustSeries.data.illust_series_first_illust, [ 'total_bookmarks', 'total_comments', 'total_view', ])).toMatchSnapshot('illust_series_first_illust'); }); it('mangaRecommended', async () => { const mangaRecommended = await pixiv.mangaRecommended(); expect(mangaRecommended.status).toBe(200); expect(mangaRecommended.data).toBeDefined(); expect(mangaRecommended.data.illusts).toBeDefined(); expect(mangaRecommended.data.illusts.length).toBeGreaterThan(0); const check = new recommended_2.GetV1MangaRecommendedCheck(); expect(() => check.throwIfResponseFailed(mangaRecommended.data)).not.toThrow(); }); it('novelDetail', async () => { const novelDetail = await pixiv.novelDetail({ novelId: 13574875, }); expect(novelDetail.status).toBe(200); expect(novelDetail.data).toBeDefined(); expect(novelDetail.data.novel).toBeDefined(); expect(novelDetail.data.novel.id).toBe(13574875); expect(novelDetail.data.novel.title).toBe('それはまるで鍵盤を奏でる細指の如く'); expect(novelDetail.data.novel.caption).toBe('色々と独自解釈、設定捏造があります。<br />張り詰めるものが多いと、人はいつか破裂する。<br /><br />Twitterやってます→【<strong><a href="https://twitter.com/somari1101" target="_blank">twitter/somari1101</a></strong>】'); expect(novelDetail.data.novel.restrict).toBe(0); expect(novelDetail.data.novel.x_restrict).toBe(0); expect(novelDetail.data.novel.is_original).toBe(false); expect(novelDetail.data.novel.image_urls).toBeDefined(); expect(novelDetail.data.novel.image_urls.large).toMatch(/^https:\/\/i\.pximg\.net\/c\/240x480_80\/novel-cover-master\/img\/.+_master1200\.jpg$/); expect(novelDetail.data.novel.image_urls.medium).toMatch(/^https:\/\/i\.pximg\.net\/c\/176x352\/novel-cover-master\/img\/.+_master1200\.jpg$/); expect(novelDetail.data.novel.image_urls.square_medium).toMatch(/^https:\/\/i\.pximg\.net\/c\/128x128\/novel-cover-master\/img\/.+_square1200\.jpg$/); expect(novelDetail.data.novel.create_date).toBe('2020-08-22T21:24:48+09:00'); expect(novelDetail.data.novel.page_count).toBe(7); expect(novelDetail.data.novel.text_length).toBe(6931); expect(novelDetail.data.novel.user).toBeDefined(); expect(novelDetail.data.novel.user.id).toBe(53003997); expect(novelDetail.data.novel.user.name).toBe('蘇鞠-そまり-'); expect(novelDetail.data.novel.user.account).toBe('user_wrmt2824'); expect(novelDetail.data.novel.user.profile_image_urls).toBeDefined(); expect(novelDetail.data.novel.user.profile_image_urls.medium).toMatch(/^https:\/\/i\.pximg\.net\/user-profile\/img\/.+\.(?:jpg|png)$/); expect(novelDetail.data.novel.user.is_followed).toBeDefined(); expect(novelDetail.data.novel.series).toStrictEqual({}); expect(novelDetail.data.novel.is_bookmarked).toBeDefined(); expect(novelDetail.data.novel.total_bookmarks).toBeGreaterThan(0); expect(novelDetail.data.novel.total_view).toBeGreaterThan(0); expect(novelDetail.data.novel.visible).toBe(true); expect(novelDetail.data.novel.total_comments).toBeGreaterThan(0); expect(novelDetail.data.novel.is_muted).toBeDefined(); expect(novelDetail.data.novel.is_mypixiv_only).toBeDefined(); expect(novelDetail.data.novel.is_x_restricted).toBe(false); expect(novelDetail.data.novel.novel_ai_type).toBe(0); const check = new detail_3.GetV2NovelDetailCheck(); expect(() => check.throwIfResponseFailed(novelDetail.data)).not.toThrow(); expect((0, utils_1.omit)(novelDetail.data.novel, [ 'total_bookmarks', 'total_comments', 'total_view', ])).toMatchSnapshot(); }); it('novelText', async () => { const novelText = await pixiv.novelText({ id: 13574875, }); expect(novelText.status).toBe(200); expect(novelText.data).toBeDefined(); }); it('novelRelated:13_574_875[novel]', async () => { const viewed = [ 19257307, 14823759, 18139010, 20311890, 19219621, 13254987, 19228582, 19692090, 20571910, 20451982, 15018893, 16748181, 15885571, 12981294, 16331599, 16962597, 16381107, ]; const novelRelated = await pixiv.novelRelated({ novelId: 13574875, seedNovelIds: [13574875], viewed, }); expect(novelRelated.status).toBe(200); expect(novelRelated.data).toBeDefined(); expect(novelRelated.data.novels).toBeDefined(); expect(novelRelated.data.novels.length).toBeGreaterThan(0); expect(novelRelated.data.novels.filter((novel) => viewed.includes(novel.id))).toHaveLength(0); const check = new related_1.GetV1NovelRelatedCheck(); expect(() => check.throwIfResponseFailed(novelRelated.data)).not.toThrow(); }); it('novelRanking', async () => { const novelRanking = await pixiv.novelRanking(); expect(novelRanking.status).toBe(200); expect(novelRanking.data).toBeDefined(); expect(novelRanking.data.novels).toBeDefined(); expect(novelRanking.data.novels.length).toBeGreaterThan(0); const check = new ranking_2.GetV1NovelRankingCheck(); expect(() => check.throwIfResponseFailed(novelRanking.data)).not.toThrow(); }); it('searchNovel', async () => { const searchNovel = await pixiv.searchNovel({ word: 'ホロライブ', }); expect(searchNovel.status).toBe(200); expect(searchNovel.data).toBeDefined(); expect(searchNovel.data.novels).toBeDefined(); expect(searchNovel.data.novels.length).toBeGreaterThan(0); const check = new novel_1.GetV1SearchNovelCheck(); expect(() => check.throwIfResponseFailed(searchNovel.data)).not.toThrow(); }); it('novelRecommended', async () => { const novelRecommended = await pixiv.novelRecommended(); expect(novelRecommended.status).toBe(200); expect(novelRecommended.data).toBeDefined(); expect(novelRecommended.data.novels).toBeDefined(); expect(novelRecommended.data.novels.length).toBeGreaterThan(0); const check = new recommended_3.GetV1NovelRecommendedCheck(); expect(() => check.throwIfResponseFailed(novelRecommended.data)).not.toThrow(); }); it('novelSeries', async () => { const novelSeries = await pixiv.novelSeries({ seriesId: 7474721, }); expect(novelSeries.status).toBe(200); expect(novelSeries.data).toBeDefined(); expect(novelSeries.data.novel_series_detail.title).toBe('夏色吹雪の恋人生活'); expect(novelSeries.data.novels).toBeDefined(); expect(novelSeries.data.novels.length).toBeGreaterThan(0); const check = new series_2.GetV2NovelSeriesCheck(); expect(() => check.throwIfResponseFailed(novelSeries.data)).not.toThrow(); expect((0, utils_1.omit)(novelSeries.data.novel_series_detail, ['content_count'])).toMatchSnapshot('novel_series_detail'); expect((0, utils_1.omit)(novelSeries.data.novel_series_first_novel, [ 'total_bookmarks', 'total_comments', 'total_view', ])).toMatchSnapshot('novel_series_first_novel'); expect((0, utils_1.omit)(novelSeries.data.novel_series_latest_novel, [ 'total_bookmarks', 'total_comments', 'total_view', ])).toMatchSnapshot('illust_series_first_illust'); }); it('userDetail', async () => { const userDetail = await pixiv.userDetail({ userId: 16568776, }); expect(userDetail.status).toBe(200); expect(userDetail.data).toBeDefined(); expect(userDetail.data.user).toBeDefined(); expect(userDetail.data.user.id).toBe(16568776); expect(userDetail.data.user.name).toBe('ihcamot'); const check = new detail_2.GetV1UserDetailCheck(); expect(() => check.throwIfResponseFailed(userDetail.data)).not.toThrow(); expect(userDetail.data.user).toMatchSnapshot('user'); expect((0, utils_1.omit)(userDetail.data.profile, [ 'total_follow_users', 'total_illust_bookmarks_public', ])).toMatchSnapshot('profile'); expect(userDetail.data.profile_publicity).toMatchSnapshot('profile_publicity'); expect(userDetail.data.workspace).toMatchSnapshot('workspace'); }); it('userBookmarksIllust', async () => { const userBookmarksIllust = await pixiv.userBookmarksIllust({ userId: 16568776, restrict: options_1.BookmarkRestrict.PRIVATE, }); expect(userBookmarksIllust.status).toBe(200); expect(userBookmarksIllust.data).toBeDefined(); expect(userBookmarksIllust.data.illusts).toBeDefined(); expect(userBookmarksIllust.data.illusts.length).toBeGreaterThan(0); const check = new illust_2.GetV1UserBookmarksIllustCheck(); expect(() => check.throwIfResponseFailed(userBookmarksIllust.data)).not.toThrow(); }); it('userBookmarksNovel', async () => { const userBookmarksNovel = await pixiv.userBookmarksNovel({ userId: 16568776, restrict: options_1.BookmarkRestrict.PRIVATE, }); expect(userBookmarksNovel.status).toBe(200); expect(userBookmarksNovel.data).toBeDefined(); expect(userBookmarksNovel.data.novels).toBeDefined(); expect(userBookmarksNovel.data.novels.length).toBeGreaterThan(0); const check = new novel_2.GetV1UserBookmarksNovelCheck(); expect(() => check.throwIfResponseFailed(userBookmarksNovel.data)).not.toThrow(); }); async function restoreBookmarkState(illustId) { await pixiv.illustBookmarkAdd({ illustId, restrict: options_1.BookmarkRestrict.PUBLIC, tags: [], }); } it('illustBookmarkAdd and illustBookmarkDelete', async () => { // テスト用のイラストID(実際に存在するイラストを使用) const illustId = 107565629; // 正しい桁区切り // テスト前にブックマーク状態を確認 const initialDetail = await pixiv.illustDetail({ illustId, }); const wasBookmarked = initialDetail.data.illust.is_bookmarked; try { // ブックマーク状態に応じてテストを実行 if (wasBookmarked) { // すでにブックマークされている場合は削除→追加のテスト const bookmarkDeleteResult = await pixiv.illustBookmarkDelete({ illustId: String(illustId), }); expect(bookmarkDeleteResult.status).toBe(200); const bookmarkAddResult = await pixiv.illustBookmarkAdd({ illustId, restrict: options_1.BookmarkRestrict.PUBLIC, tags: ['テスト'], }); expect(bookmarkAddResult.status).toBe(200); } else { // ブックマークされていない場合は追加→削除のテスト const bookmarkAddResult = await pixiv.illustBookmarkAdd({ illustId, restrict: options_1.BookmarkRestrict.PUBLIC, tags: ['テスト'], }); expect(bookmarkAddResult.status).toBe(200); const bookmarkDeleteResult = await pixiv.illustBookmarkDelete({ illustId: String(illustId), }); expect(bookmarkDeleteResult.status).toBe(200); } } finally { await restoreBookmarkState(illustId); } }); it('novelBookmarkAdd and novelBookmarkDelete', async () => { // テスト用の小説ID(実際に存在する小説を使用) const novelId = 13574875; // 数値型としてdetail取得用に定義(正しい桁区切り) const novelIdStr = '13574875'; // 文字列型としてブックマーク用に定義 // テスト前にブックマーク状態を確認 const initialDetail = await pixiv.novelDetail({ novelId, }); const wasBookmarked = initialDetail.data.novel.is_bookmarked; try { // ブックマーク状態に応じてテストを実行 if (wasBookmarked) { // すでにブックマークされている場合は削除→追加のテスト const novelBookmarkDeleteResult = await pixiv.novelBookmarkDelete({ novelId: novelIdStr, // 文字列型を渡す }); expect(novelBookmarkDeleteResult.status).toBe(200); const novelBookmarkAddResult = await pixiv.novelBookmarkAdd({ novelId: novelIdStr, // 文字列型を渡す restrict: options_1.BookmarkRestrict.PUBLIC, tags: ['テスト'], }); expect(novelBookmarkAddResult.status).toBe(200); } else { // ブックマークされていない場合は追加→削除のテスト const novelBookmarkAddResult = await pixiv.novelBookmarkAdd({ novelId: novelIdStr, // 文字列型を渡す restrict: options_1.BookmarkRestrict.PUBLIC, tags: ['テスト'], }); expect(novelBookmarkAddResult.status).toBe(200); const novelBookmarkDeleteResult = await pixiv.novelBookmarkDelete({ novelId: novelIdStr, // 文字列型を渡す }); expect(novelBookmarkDeleteResult.status).toBe(200); } } finally { // テスト後に元のブックマーク状態に戻す if (wasBookmarked) { // 元々ブックマークされていた場合、ブックマークを確実に追加 await pixiv.novelBookmarkAdd({ novelId: novelIdStr, // 文字列型を渡す restrict: options_1.BookmarkRestrict.PUBLIC, tags: [], }); } else { // 元々ブックマークされていなかった場合、ブックマークを確実に削除 try { await pixiv.novelBookmarkDelete({ novelId: novelIdStr, // 文字列型を渡す }); } catch { // すでに削除済みの場合はエラーを無視 // エラーハンドリング用のコメント } } } }); describe('utility methods', () => { it('parseQueryString', () => { const qs = 'https://example.com?a=1&b=2&c=3'; const parsed = pixiv_1.default.parseQueryString(qs); expect(parsed).toStrictEqual({ a: '1', b: '2', c: '3', }); }); it('isError', () => { const error = { error: { user_message: 'Rate limit exceeded.', message: '', reason: '', }, }; expect(pixiv_1.default.isError(error)).toBe(true); const notError = { data: { result: 'success', }, }; expect(pixiv_1.default.isError(notError)).toBe(false); }); it('checkRequiredOptions', () => { // thisバインディングを持つアロー関数として定義(バインディング問題を解決) const pixivInstance = pixiv; const checkRequiredOptions = (options, requiredOptions) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access return pixivInstance.checkRequiredOptions(options, requiredOptions); }; expect(() => { checkRequiredOptions({ illustId: 123 }, ['illustId']); }).not.toThrow(); expect(() => { checkRequiredOptions({}, ['illustId']); }).toThrow('Missing required option: illustId'); expect(() => { checkRequiredOptions({ illustId: 123, userId: 456 }, [ 'illustId', 'userId', ]); }).not.toThrow(); expect(() => { checkRequiredOptions({ illustId: 123 }, ['illustId', 'userId']); }).toThrow('Missing required option: userId'); }); it('convertCamelToSnake', () => { // thisバインディングを持つアロー関数として定義 const pixivInstance = pixiv; const convertCamelToSnake = (obj) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access return pixivInstance.convertCamelToSnake(obj); }; expect(convertCamelToSnake({ illustId: 123, maxBookmarkId: 456, userDetailInfo: true, })).toStrictEqual({ illust_id: 123, max_bookmark_id: 456, user_detail_info: true, }); expect(convertCamelToSnake({ id: 123, name: 'test', })).toStrictEqual({ id: 123, name: 'test', }); }); it('isJSON', () => { // thisバインディングを持つアロー関数として定義 const pixivInstance = pixiv; const isJSON = (value) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access return pixivInstance.isJSON(value); }; expect(isJSON({ key: 'value' })).toBe(true); expect(isJSON('{"key":"value"}')).toBe(true); expect(isJSON('invalid json')).toBe(false); }); }); }); describe('Pixiv class coverage tests', () => { describe('of method', () => { // 行245のカバレッジ: トークンリフレッシュ失敗のテスト it('should throw an error when refresh token fails', async () => { // モック化してエラーを起こす const originalAxiosPost = axios_1.default.post; try { // axiosをモック化し、トークンリフレッシュが失敗するようにする axios_1.default.post = jest.fn().mockResolvedValueOnce({ status: 401, data: { error: 'invalid_token', message: 'Invalid refresh token', }, }); await expect(pixiv_1.default.of('invalid_refresh_token')).rejects.toThrow('Failed to refresh token'); } finally { // モックを元に戻す axios_1.default.post = originalAxiosPost; } }); // 行276のカバレッジ: データベースなしの場合 it('should initialize without response database', async () => { const originalAxiosPost = axios_1.default.post; try { // モックでPOSTメソッドが成功を返すようにする axios_1.default.post = jest.fn().mockResolvedValueOnce({ status: 200, data: { user: { id: 'test_user_id' }, response: { access_token: 'test_access_token', refresh_token: 'test_refresh_token', }, }, }); // データベースオプションなしでインスタンス化 const instance = await pixiv_1.default.of('test_refresh_token', { debugOptions: { outputResponse: { enable: false, }, }, }); expect(instance).toBeInstanceOf(pixiv_1.default); await instance.close(); } finally { // モックを元に戻す axios_1.default.post = originalAxiosPost; } }); }); describe('request method', () => { let pixiv; let requestSpy; beforeAll(async () => { // テスト用のPixivインスタンスを生成 const originalAxiosPost = axios_1.default.post; try { // モックでPOSTメソッドが成功を返すようにする axios_1.default.post = jest.fn().mockResolvedValueOnce({ status: 200, data: { user: { id: 'test_user_id' }, response: { access_token: 'test_access_token', refresh_token: 'test_refresh_token', }, }, }); pixiv = await pixiv_1.default.of('test_refresh_token'); } finally { // モックを元に戻す axios_1.default.post = originalAxiosPost; } }); afterAll(async () => { // テスト終了後にインスタンスをクリーンアップ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (pixiv) { await pixiv.close(); } }); beforeEach(() => { // requestメソッドをスパイ requestSpy = jest.spyOn(pixiv, 'request'); }); afterEach(() => { // スパイをリセット requestSpy.mockRestore(); }); // 行898のカバレッジ: 無効なHTTPメソッド it('should throw an error with invalid HTTP method', async () => { // 基本的にrequest()は直接呼び出せないのでモックして内部で例外を投げさせる requestSpy.mockImplementationOnce(() => { throw new Error('Invalid method'); }); await expect(pixiv.illustDetail({ illustId: 123 })).rejects.toThrow('Invalid method'); }); // 行906のカバレッジ: レスポンスURLフォールバック it('should use fallback URL when responseUrl is undefined', async () => { // saveResponseメソッドをスパイ const saveResponseSpy = jest.spyOn(pixiv, 'saveResponse'); // 元のaxios.getをバックアップ const originalAxiosGet = pixiv.axios.get; try { // モック化 pixiv.axios.get = jest.fn().mockResolvedValueOnce({ status: 200, data: { test: 'data' }, config: { headers: {}, data: null, }, headers: {}, request: { res: {}, // responseUrlプロパティがない }, }); // レスポンスデータベースが設定されていれば実行 // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (pixiv.responseDatabase) { await pixiv.illustDetail({ illustId: 123 }); // saveResponseが呼び出されたことを確認 expect(saveResponseSpy).toHaveBeenCalled(); } else { // レスポンスデータベースがない場合はテストをスキップ console.log('Test skipped: responseDatabase is not available'); } } finally { // モックを元に戻す pixiv.axios.get = originalAxiosGet; saveResponseSpy.mockRestore(); } }); }); }); //# sourceMappingURL=pixiv.test.js.map