Skip to content

Commit 3c404e5

Browse files
committed
[YouTube] Add custom mock tests for Premiere StreamInfoItems
The JSON data for these tests was obtained manually.
1 parent e50f499 commit 3c404e5

4 files changed

Lines changed: 824 additions & 8 deletions

File tree

extractor/src/test/java/org/schabi/newpipe/downloader/DownloaderFactory.java

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
import java.util.Locale;
66

7+
import javax.annotation.Nonnull;
8+
import javax.annotation.Nullable;
9+
710
public class DownloaderFactory {
811

912
private static final DownloaderType DEFAULT_DOWNLOADER = DownloaderType.REAL;
@@ -36,20 +39,31 @@ private static DownloaderType determineDownloaderType() {
3639
}
3740

3841
public static Downloader getDownloader(final Class<?> clazz) {
39-
return getDownloader(clazz, null);
42+
return getDownloader(getMockPath(clazz, null));
43+
}
44+
45+
public static Downloader getDownloader(final Class<?> clazz,
46+
@Nullable final String specificUseCase) {
47+
return getDownloader(getMockPath(clazz, specificUseCase));
4048
}
4149

42-
public static Downloader getDownloader(final Class<?> clazz, final String specificUseCase) {
50+
/**
51+
* Always returns a path without a trailing '/', so that it can be used both as a folder name
52+
* and as a filename. The {@link MockDownloader} will use it as a folder name, but other tests
53+
* can use it as a filename, if only one custom mock file is needed for that test.
54+
*/
55+
public static String getMockPath(final Class<?> clazz,
56+
@Nullable final String specificUseCase) {
4357
String baseName = clazz.getName();
4458
if (specificUseCase != null) {
4559
baseName += "." + specificUseCase;
4660
}
47-
return getDownloader("src/test/resources/mocks/v1/"
48-
+ baseName
49-
.toLowerCase(Locale.ENGLISH)
50-
.replace('$', '.')
51-
.replace("test", "")
52-
.replace('.', '/'));
61+
return "src/test/resources/mocks/v1/"
62+
+ baseName
63+
.toLowerCase(Locale.ENGLISH)
64+
.replace('$', '.')
65+
.replace("test", "")
66+
.replace('.', '/');
5367
}
5468

5569
/**
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package org.schabi.newpipe.extractor.services.youtube;
2+
3+
import static org.junit.jupiter.api.Assertions.assertAll;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertFalse;
6+
import static org.junit.jupiter.api.Assertions.assertNotNull;
7+
import static org.junit.jupiter.api.Assertions.assertNull;
8+
import static org.junit.jupiter.api.Assertions.assertTrue;
9+
import static org.schabi.newpipe.downloader.DownloaderFactory.getMockPath;
10+
11+
import com.grack.nanojson.JsonParser;
12+
import com.grack.nanojson.JsonParserException;
13+
14+
import org.junit.jupiter.api.Test;
15+
import org.schabi.newpipe.extractor.localization.Localization;
16+
import org.schabi.newpipe.extractor.localization.TimeAgoPatternsManager;
17+
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamInfoItemExtractor;
18+
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamInfoItemLockupExtractor;
19+
import org.schabi.newpipe.extractor.stream.StreamType;
20+
21+
import java.io.FileInputStream;
22+
import java.io.FileNotFoundException;
23+
import java.time.OffsetDateTime;
24+
import java.time.ZoneOffset;
25+
26+
public class YoutubeStreamInfoItemTest {
27+
@Test
28+
void videoRendererPremiere() throws FileNotFoundException, JsonParserException {
29+
final var json = JsonParser.object().from(new FileInputStream(getMockPath(
30+
YoutubeStreamInfoItemTest.class, "videoRendererPremiere") + ".json"));
31+
final var timeAgoParser = TimeAgoPatternsManager.getTimeAgoParserFor(Localization.DEFAULT);
32+
final var extractor = new YoutubeStreamInfoItemExtractor(json, timeAgoParser);
33+
assertAll(
34+
() -> assertEquals(StreamType.VIDEO_STREAM, extractor.getStreamType()),
35+
() -> assertFalse(extractor.isAd()),
36+
() -> assertEquals("https://www.youtube.com/watch?v=M_8QNw_JM4I", extractor.getUrl()),
37+
() -> assertEquals("This video will premiere in 6 months.", extractor.getName()),
38+
() -> assertEquals(33, extractor.getDuration()),
39+
() -> assertEquals("Blunt Brothers Productions", extractor.getUploaderName()),
40+
() -> assertEquals("https://www.youtube.com/channel/UCUPrbbdnot-aPgNM65svgOg", extractor.getUploaderUrl()),
41+
() -> assertFalse(extractor.getUploaderAvatars().isEmpty()),
42+
() -> assertTrue(extractor.isUploaderVerified()),
43+
() -> assertEquals("2026-03-15 13:12", extractor.getTextualUploadDate()),
44+
() -> {
45+
assertNotNull(extractor.getUploadDate());
46+
assertEquals(OffsetDateTime.of(2026, 3, 15, 13, 12, 0, 0, ZoneOffset.UTC), extractor.getUploadDate().offsetDateTime());
47+
},
48+
() -> assertEquals(-1, extractor.getViewCount()),
49+
() -> assertFalse(extractor.getThumbnails().isEmpty()),
50+
() -> assertEquals("Patience is key… MERCH SHOP : https://www.bluntbrosproductions.com Follow us on Instagram for early updates: ...", extractor.getShortDescription()),
51+
() -> assertFalse(extractor.isShortFormContent())
52+
);
53+
}
54+
55+
@Test
56+
void lockupViewModelPremiere()
57+
throws FileNotFoundException, JsonParserException {
58+
final var json = JsonParser.object().from(new FileInputStream(getMockPath(
59+
YoutubeStreamInfoItemTest.class, "lockupViewModelPremiere") + ".json"));
60+
final var timeAgoParser = TimeAgoPatternsManager.getTimeAgoParserFor(Localization.DEFAULT);
61+
final var extractor = new YoutubeStreamInfoItemLockupExtractor(json, timeAgoParser);
62+
assertAll(
63+
() -> assertEquals(StreamType.VIDEO_STREAM, extractor.getStreamType()),
64+
() -> assertFalse(extractor.isAd()),
65+
() -> assertEquals("https://www.youtube.com/watch?v=VIDEO_ID", extractor.getUrl()),
66+
() -> assertEquals("VIDEO_TITLE", extractor.getName()),
67+
() -> assertEquals(-1, extractor.getDuration()),
68+
() -> assertEquals("VIDEO_CHANNEL_NAME", extractor.getUploaderName()),
69+
() -> assertEquals("https://www.youtube.com/channel/UCD_on7-zu7Zuc3zissQvrgw", extractor.getUploaderUrl()),
70+
() -> assertFalse(extractor.getUploaderAvatars().isEmpty()),
71+
() -> assertFalse(extractor.isUploaderVerified()),
72+
() -> assertEquals("14/08/2025, 13:00", extractor.getTextualUploadDate()),
73+
() -> {
74+
assertNotNull(extractor.getUploadDate());
75+
assertEquals(OffsetDateTime.of(2025, 8, 14, 13, 0, 0, 0, ZoneOffset.UTC), extractor.getUploadDate().offsetDateTime());
76+
},
77+
() -> assertEquals(-1, extractor.getViewCount()),
78+
() -> assertFalse(extractor.getThumbnails().isEmpty()),
79+
() -> assertNull(extractor.getShortDescription()),
80+
() -> assertFalse(extractor.isShortFormContent())
81+
);
82+
}
83+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
{
2+
"contentImage": {
3+
"thumbnailViewModel": {
4+
"image": {
5+
"sources": [
6+
{
7+
"url": "https://i.ytimg.com/vi/0_-Nh-nOhLQ/hqdefault.jpg?sqp=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&rs=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
8+
"width": 168,
9+
"height": 94
10+
},
11+
{
12+
"url": "https://i.ytimg.com/vi/0_-Nh-nOhLQ/hqdefault.jpg?sqp=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&rs=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
13+
"width": 336,
14+
"height": 188
15+
}
16+
]
17+
},
18+
"overlays": [
19+
{
20+
"thumbnailOverlayBadgeViewModel": {
21+
"thumbnailBadges": [
22+
{
23+
"thumbnailBadgeViewModel": {
24+
"text": "Upcoming",
25+
"badgeStyle": "THUMBNAIL_OVERLAY_BADGE_STYLE_DEFAULT",
26+
"animationActivationTargetId": "VIDEO_ID",
27+
"animationActivationEntityKey": "REDACTED",
28+
"lottieData": {
29+
"url": "https://www.gstatic.com/youtube/img/lottie/audio_indicator/audio_indicator_v2.json",
30+
"settings": {
31+
"loop": true,
32+
"autoplay": true
33+
}
34+
},
35+
"animatedText": "Now playing",
36+
"animationActivationEntitySelectorType": "THUMBNAIL_BADGE_ANIMATION_ENTITY_SELECTOR_TYPE_PLAYER_STATE"
37+
}
38+
}
39+
],
40+
"position": "THUMBNAIL_OVERLAY_BADGE_POSITION_BOTTOM_END"
41+
}
42+
},
43+
{
44+
"thumbnailHoverOverlayToggleActionsViewModel": {
45+
"buttons": []
46+
}
47+
}
48+
]
49+
}
50+
},
51+
"metadata": {
52+
"lockupMetadataViewModel": {
53+
"title": {
54+
"content": "VIDEO_TITLE"
55+
},
56+
"image": {
57+
"decoratedAvatarViewModel": {
58+
"avatar": {
59+
"avatarViewModel": {
60+
"image": {
61+
"sources": [
62+
{
63+
"url": "https://yt3.ggpht.com/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
64+
"width": 68,
65+
"height": 68
66+
}
67+
]
68+
},
69+
"avatarImageSize": "AVATAR_SIZE_M"
70+
}
71+
},
72+
"a11yLabel": "Go to channel",
73+
"rendererContext": {
74+
"commandContext": {
75+
"onTap": {
76+
"innertubeCommand": {
77+
"clickTrackingParams": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
78+
"commandMetadata": {
79+
"webCommandMetadata": {
80+
"url": "/@deep_flow_music_ua",
81+
"webPageType": "WEB_PAGE_TYPE_CHANNEL",
82+
"rootVe": 3611,
83+
"apiUrl": "/youtubei/v1/browse"
84+
}
85+
},
86+
"browseEndpoint": {
87+
"browseId": "UCD_on7-zu7Zuc3zissQvrgw",
88+
"canonicalBaseUrl": "/@deep_flow_music_ua"
89+
}
90+
}
91+
}
92+
}
93+
}
94+
}
95+
},
96+
"metadata": {
97+
"contentMetadataViewModel": {
98+
"metadataRows": [
99+
{
100+
"metadataParts": [
101+
{
102+
"text": {
103+
"content": "VIDEO_CHANNEL_NAME",
104+
"styleRuns": [],
105+
"attachmentRuns": []
106+
}
107+
}
108+
]
109+
},
110+
{
111+
"metadataParts": [
112+
{
113+
"text": {
114+
"content": "56 waiting"
115+
}
116+
},
117+
{
118+
"text": {
119+
"content": "Premieres 14/08/2025, 13:00"
120+
}
121+
}
122+
]
123+
}
124+
],
125+
"delimiter": ""
126+
}
127+
},
128+
"menuButton": {}
129+
}
130+
},
131+
"contentId": "VIDEO_ID",
132+
"contentType": "LOCKUP_CONTENT_TYPE_VIDEO"
133+
}

0 commit comments

Comments
 (0)