fix(tests): fix all cypress tests
This commit is contained in:
@@ -1,92 +0,0 @@
|
||||
describe('TVDB Integration', () => {
|
||||
// Constants for routes and selectors
|
||||
const ROUTES = {
|
||||
home: '/',
|
||||
tvdbSettings: '/settings/tvdb',
|
||||
tomorrowIsOursTvShow: '/tv/72879',
|
||||
monsterTvShow: '/tv/225634',
|
||||
};
|
||||
|
||||
const SELECTORS = {
|
||||
sidebarToggle: '[data-testid=sidebar-toggle]',
|
||||
sidebarSettingsMobile: '[data-testid=sidebar-menu-settings-mobile]',
|
||||
settingsNavDesktop: 'nav[data-testid="settings-nav-desktop"]',
|
||||
tvdbEnable: 'input[data-testid="tvdb-enable"]',
|
||||
tvdbSaveButton: '[data-testid=tvbd-save-button]',
|
||||
heading: '.heading',
|
||||
season1: 'Season 1',
|
||||
season2: 'Season 2',
|
||||
};
|
||||
|
||||
// Reusable commands
|
||||
const toggleTVDBSetting = () => {
|
||||
cy.intercept('/api/v1/settings/tvdb').as('tvdbRequest');
|
||||
cy.get(SELECTORS.tvdbSaveButton).click();
|
||||
return cy.wait('@tvdbRequest');
|
||||
};
|
||||
|
||||
const verifyTVDBResponse = (response, expectedUseValue) => {
|
||||
expect(response.statusCode).to.equal(200);
|
||||
expect(response.body.tvdb).to.equal(expectedUseValue);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// Perform login
|
||||
cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));
|
||||
|
||||
// Navigate to TVDB settings
|
||||
cy.visit(ROUTES.home);
|
||||
cy.get(SELECTORS.sidebarToggle).click();
|
||||
cy.get(SELECTORS.sidebarSettingsMobile).click();
|
||||
cy.get(
|
||||
`${SELECTORS.settingsNavDesktop} a[href="${ROUTES.tvdbSettings}"]`
|
||||
).click();
|
||||
|
||||
// Verify heading
|
||||
cy.get(SELECTORS.heading).should('contain', 'Tvdb');
|
||||
|
||||
// Configure TVDB settings
|
||||
cy.get(SELECTORS.tvdbEnable).then(($checkbox) => {
|
||||
const isChecked = $checkbox.is(':checked');
|
||||
|
||||
if (!isChecked) {
|
||||
// If disabled, enable TVDB
|
||||
cy.wrap($checkbox).click();
|
||||
toggleTVDBSetting().then(({ response }) => {
|
||||
verifyTVDBResponse(response, true);
|
||||
});
|
||||
} else {
|
||||
// If enabled, disable then re-enable TVDB
|
||||
cy.wrap($checkbox).click();
|
||||
toggleTVDBSetting().then(({ response }) => {
|
||||
verifyTVDBResponse(response, false);
|
||||
});
|
||||
|
||||
cy.wrap($checkbox).click();
|
||||
toggleTVDBSetting().then(({ response }) => {
|
||||
verifyTVDBResponse(response, true);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should display "Tomorrow is Ours" show information correctly (1 season on TMDB >1 seasons on TVDB)', () => {
|
||||
cy.visit(ROUTES.tomorrowIsOursTvShow);
|
||||
cy.contains(SELECTORS.season2)
|
||||
.should('be.visible')
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
});
|
||||
|
||||
it('Should display "Monster" show information correctly (Not existing on TVDB)', () => {
|
||||
cy.visit(ROUTES.monsterTvShow);
|
||||
cy.intercept('/api/v1/tv/225634/season/1').as('season1');
|
||||
cy.contains(SELECTORS.season1)
|
||||
.should('be.visible')
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
cy.wait('@season1');
|
||||
|
||||
cy.contains('9 - Hang Men').should('be.visible');
|
||||
});
|
||||
});
|
||||
127
cypress/e2e/providers/tvdb.cy.ts
Normal file
127
cypress/e2e/providers/tvdb.cy.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
describe('TVDB Integration', () => {
|
||||
// Constants for routes and selectors
|
||||
const ROUTES = {
|
||||
home: '/',
|
||||
metadataSettings: '/settings/metadata',
|
||||
tomorrowIsOursTvShow: '/tv/72879',
|
||||
monsterTvShow: '/tv/225634',
|
||||
};
|
||||
|
||||
const SELECTORS = {
|
||||
sidebarToggle: '[data-testid=sidebar-toggle]',
|
||||
sidebarSettingsMobile: '[data-testid=sidebar-menu-settings-mobile]',
|
||||
settingsNavDesktop: 'nav[data-testid="settings-nav-desktop"]',
|
||||
metadataTestButton: 'button[type="button"]:contains("Test")',
|
||||
metadataSaveButton: '[data-testid="metadata-save-button"]',
|
||||
tmdbStatus: '[data-testid="tmdb-status"]',
|
||||
tvdbStatus: '[data-testid="tvdb-status"]',
|
||||
tvIndexerSelector: '[data-testid="tv-indexer-selector"]',
|
||||
animeIndexerSelector: '[data-testid="anime-indexer-selector"]',
|
||||
seasonSelector: '[data-testid="season-selector"]',
|
||||
season1: 'Season 1',
|
||||
season2: 'Season 2',
|
||||
episodeList: '[data-testid="episode-list"]',
|
||||
episode9: '9 - Hang Men',
|
||||
};
|
||||
|
||||
// Reusable commands
|
||||
const navigateToMetadataSettings = () => {
|
||||
cy.visit(ROUTES.home);
|
||||
cy.get(SELECTORS.sidebarToggle).click();
|
||||
cy.get(SELECTORS.sidebarSettingsMobile).click();
|
||||
cy.get(
|
||||
`${SELECTORS.settingsNavDesktop} a[href="${ROUTES.metadataSettings}"]`
|
||||
).click();
|
||||
};
|
||||
|
||||
const testAndVerifyMetadataConnection = () => {
|
||||
cy.intercept('POST', '/api/v1/settings/metadatas/test').as(
|
||||
'testConnection'
|
||||
);
|
||||
cy.get(SELECTORS.metadataTestButton).click();
|
||||
return cy.wait('@testConnection');
|
||||
};
|
||||
|
||||
const saveMetadataSettings = (customBody = null) => {
|
||||
// Si un corps personnalisé est fourni, utilisez-le pour modifier la requête
|
||||
if (customBody) {
|
||||
cy.intercept('PUT', '/api/v1/settings/metadatas', (req) => {
|
||||
req.body = customBody;
|
||||
}).as('saveMetadata');
|
||||
} else {
|
||||
// Sinon, juste intercepter sans modifier
|
||||
cy.intercept('PUT', '/api/v1/settings/metadatas').as('saveMetadata');
|
||||
}
|
||||
|
||||
cy.get(SELECTORS.metadataSaveButton).click();
|
||||
return cy.wait('@saveMetadata');
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// Perform login
|
||||
cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));
|
||||
|
||||
// Navigate to Metadata settings
|
||||
navigateToMetadataSettings();
|
||||
|
||||
// Verify we're on the correct settings page
|
||||
cy.contains('h3', 'Metadata').should('be.visible');
|
||||
|
||||
// Configure TVDB as TV provider and test connection
|
||||
// Supposons que vous avez ajouté data-testid au div parent du Select
|
||||
cy.get('[data-testid="tv-indexer-selector"]').click();
|
||||
|
||||
// Test the connection
|
||||
testAndVerifyMetadataConnection().then(({ response }) => {
|
||||
expect(response.statusCode).to.equal(200);
|
||||
// Check TVDB connection status
|
||||
cy.get(SELECTORS.tvdbStatus).should('contain', 'Operational');
|
||||
});
|
||||
|
||||
// Save settings
|
||||
saveMetadataSettings({
|
||||
anime: 'tvdb',
|
||||
tv: 'tvdb',
|
||||
}).then(({ response }) => {
|
||||
expect(response.statusCode).to.equal(200);
|
||||
expect(response.body.tv).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should display "Tomorrow is Ours" show information with multiple seasons from TVDB', () => {
|
||||
// Navigate to the TV show
|
||||
cy.visit(ROUTES.tomorrowIsOursTvShow);
|
||||
|
||||
// Verify that multiple seasons are displayed (TMDB has only 1 season, TVDB has multiple)
|
||||
//cy.get(SELECTORS.seasonSelector).should('exist');
|
||||
cy.intercept('/api/v1/tv/225634/season/1').as('season1');
|
||||
// Select Season 2 and verify it loads
|
||||
cy.contains(SELECTORS.season2)
|
||||
.should('be.visible')
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
|
||||
// Verify that episodes are displayed for Season 2
|
||||
cy.contains('260 - Episode 506').should('be.visible');
|
||||
});
|
||||
|
||||
it('Should display "Monster" show information correctly when not existing on TVDB', () => {
|
||||
// Navigate to the TV show
|
||||
cy.visit(ROUTES.monsterTvShow);
|
||||
|
||||
// Intercept season 1 request
|
||||
cy.intercept('/api/v1/tv/225634/season/1').as('season1');
|
||||
|
||||
// Select Season 1
|
||||
cy.contains(SELECTORS.season1)
|
||||
.should('be.visible')
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
|
||||
// Wait for the season data to load
|
||||
cy.wait('@season1');
|
||||
|
||||
// Verify specific episode exists
|
||||
cy.contains(SELECTORS.episode9).should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -20,12 +20,14 @@ import logger from '@server/logger';
|
||||
interface TvdbConfig {
|
||||
baseUrl: string;
|
||||
maxRequestsPerSecond: number;
|
||||
maxRequests: number;
|
||||
cachePrefix: AvailableCacheIds;
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: TvdbConfig = {
|
||||
baseUrl: 'https://api4.thetvdb.com/v4',
|
||||
maxRequestsPerSecond: 50,
|
||||
maxRequests: 20,
|
||||
cachePrefix: 'tvdb' as const,
|
||||
};
|
||||
|
||||
@@ -52,8 +54,8 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
|
||||
{
|
||||
nodeCache: cacheManager.getCache(finalConfig.cachePrefix).data,
|
||||
rateLimit: {
|
||||
maxRequests: finalConfig.maxRequests,
|
||||
maxRPS: finalConfig.maxRequestsPerSecond,
|
||||
id: finalConfig.cachePrefix,
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -112,14 +114,25 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
|
||||
}
|
||||
|
||||
async login(): Promise<TvdbLoginResponse> {
|
||||
let body: { apiKey: string; pin?: string } = {
|
||||
apiKey:
|
||||
process.env.TVDB_API_KEY || 'e4428e99-1c35-4500-9534-e13c1193b428',
|
||||
};
|
||||
|
||||
if (this.pin) {
|
||||
body = {
|
||||
...body,
|
||||
pin: this.pin,
|
||||
};
|
||||
}
|
||||
|
||||
const response = await this.post<TvdbBaseResponse<TvdbLoginResponse>>(
|
||||
'/login',
|
||||
{
|
||||
apiKey: process.env.TVDB_API_KEY,
|
||||
...body,
|
||||
}
|
||||
);
|
||||
|
||||
this.defaultHeaders.Authorization = `Bearer ${response.data.token}`;
|
||||
this.token = response.data.token;
|
||||
|
||||
return response.data;
|
||||
@@ -250,9 +263,11 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
|
||||
|
||||
private async fetchTvdbShowData(tvdbId: number): Promise<TvdbTvDetails> {
|
||||
const resp = await this.get<TvdbBaseResponse<TvdbTvDetails>>(
|
||||
`/series/${tvdbId}/extended?meta=episodes`,
|
||||
`/series/${tvdbId}/extended?meta=episodes&short=true`,
|
||||
{
|
||||
short: 'true',
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
},
|
||||
},
|
||||
Tvdb.DEFAULT_CACHE_TTL
|
||||
);
|
||||
@@ -265,12 +280,15 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
|
||||
return [];
|
||||
}
|
||||
|
||||
return tvdbData.seasons
|
||||
const seasons = tvdbData.seasons
|
||||
.filter(
|
||||
(season) =>
|
||||
season.number > 0 && season.type && season.type.type === 'official'
|
||||
)
|
||||
.sort((a, b) => a.number - b.number)
|
||||
.map((season) => this.createSeasonData(season, tvdbData));
|
||||
|
||||
return seasons;
|
||||
}
|
||||
|
||||
private createSeasonData(
|
||||
@@ -307,18 +325,38 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
|
||||
private async getTvdbSeasonData(
|
||||
tvdbId: number,
|
||||
seasonNumber: number,
|
||||
tvId: number,
|
||||
language: string = Tvdb.DEFAULT_LANGUAGE
|
||||
tvId: number
|
||||
//language: string = Tvdb.DEFAULT_LANGUAGE
|
||||
): Promise<TmdbSeasonWithEpisodes> {
|
||||
const tvdbData = await this.fetchTvdbShowData(tvdbId);
|
||||
|
||||
if (!tvdbData) {
|
||||
logger.error(`Failed to fetch TVDB data for ID: ${tvdbId}`);
|
||||
return this.createEmptySeasonResponse(tvId);
|
||||
}
|
||||
|
||||
// get season id
|
||||
const season = tvdbData.seasons.find(
|
||||
(season) =>
|
||||
season.number === seasonNumber &&
|
||||
season.type.type &&
|
||||
season.type.type === 'official'
|
||||
);
|
||||
|
||||
if (!season) {
|
||||
logger.error(
|
||||
`Failed to find season ${seasonNumber} for TVDB ID: ${tvdbId}`
|
||||
);
|
||||
return this.createEmptySeasonResponse(tvId);
|
||||
}
|
||||
|
||||
const resp = await this.get<TvdbBaseResponse<TvdbSeasonDetails>>(
|
||||
`/series/${tvdbId}/episodes/official/${language}`,
|
||||
{}
|
||||
`/seasons/${season.id}/extended`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const seasons = resp.data;
|
||||
@@ -342,6 +380,7 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
|
||||
tvId: number
|
||||
): TmdbTvEpisodeResult[] {
|
||||
if (!tvdbSeason || !tvdbSeason.episodes) {
|
||||
logger.error('No episodes found in TVDB season data');
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -355,6 +394,10 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
|
||||
index: number,
|
||||
tvId: number
|
||||
): TmdbTvEpisodeResult {
|
||||
logger.info(
|
||||
`Creating episode data for episode: ${episode.name} with index: ${index}`
|
||||
);
|
||||
|
||||
return {
|
||||
id: episode.id,
|
||||
air_date: episode.aired,
|
||||
@@ -364,9 +407,7 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
|
||||
season_number: episode.seasonNumber,
|
||||
production_code: '',
|
||||
show_id: tvId,
|
||||
still_path: episode.image
|
||||
? 'https://artworks.thetvdb.com' + episode.image
|
||||
: '',
|
||||
still_path: episode.image ? episode.image : '',
|
||||
vote_average: 1,
|
||||
vote_count: 1,
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ enum IndexerType {
|
||||
}
|
||||
|
||||
type IndexerOptionType = {
|
||||
testId?: string;
|
||||
value: IndexerType;
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
@@ -23,12 +24,14 @@ const messages = defineMessages('components.MetadataSelector', {
|
||||
});
|
||||
|
||||
interface MetadataSelectorProps {
|
||||
testId: string;
|
||||
value: IndexerType;
|
||||
onChange: (value: IndexerType) => void;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
const MetadataSelector = ({
|
||||
testId = 'indexer-selector',
|
||||
value,
|
||||
onChange,
|
||||
isDisabled = false,
|
||||
@@ -37,11 +40,13 @@ const MetadataSelector = ({
|
||||
|
||||
const indexerOptions: IndexerOptionType[] = [
|
||||
{
|
||||
testId: 'tmdb-option',
|
||||
value: IndexerType.TMDB,
|
||||
label: intl.formatMessage(messages.tmdbLabel),
|
||||
icon: <TmdbLogo />,
|
||||
},
|
||||
{
|
||||
testId: 'tvdb-option',
|
||||
value: IndexerType.TVDB,
|
||||
label: intl.formatMessage(messages.tvdbLabel),
|
||||
icon: <TvdbLogo />,
|
||||
@@ -64,26 +69,28 @@ const MetadataSelector = ({
|
||||
const formatOptionLabel = (option: IndexerOptionType) => (
|
||||
<div className="flex items-center">
|
||||
{option.icon}
|
||||
<span>{option.label}</span>
|
||||
<span data-testid={option.testId}>{option.label}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
options={indexerOptions}
|
||||
isDisabled={isDisabled}
|
||||
className="react-select-container"
|
||||
classNamePrefix="react-select"
|
||||
value={indexerOptions.find((option) => option.value === value)}
|
||||
onChange={(selectedOption) => {
|
||||
if (selectedOption) {
|
||||
onChange(selectedOption.value);
|
||||
}
|
||||
}}
|
||||
placeholder={intl.formatMessage(messages.selectIndexer)}
|
||||
styles={customStyles}
|
||||
formatOptionLabel={formatOptionLabel}
|
||||
/>
|
||||
<div data-testid={testId}>
|
||||
<Select
|
||||
options={indexerOptions}
|
||||
isDisabled={isDisabled}
|
||||
className="react-select-container"
|
||||
classNamePrefix="react-select"
|
||||
value={indexerOptions.find((option) => option.value === value)}
|
||||
onChange={(selectedOption) => {
|
||||
if (selectedOption) {
|
||||
onChange(selectedOption.value);
|
||||
}
|
||||
}}
|
||||
placeholder={intl.formatMessage(messages.selectIndexer)}
|
||||
styles={customStyles}
|
||||
formatOptionLabel={formatOptionLabel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -197,16 +197,22 @@ const SettingsMetadata = () => {
|
||||
</h4>
|
||||
<div className="flex flex-col space-y-3">
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 w-12">TheMovieDB:</span>
|
||||
<span className={`text-sm ${getStatusClass(providerStatus.tmdb)}`}>
|
||||
<span className="mr-2 w-24">TheMovieDB:</span>
|
||||
<span
|
||||
className={`text-sm ${getStatusClass(providerStatus.tmdb)}`}
|
||||
data-testid="tmdb-status-container"
|
||||
>
|
||||
<Badge badgeType={getBadgeType(providerStatus.tmdb)}>
|
||||
{getStatusMessage(providerStatus.tmdb)}
|
||||
</Badge>
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 w-12">TheTVDB:</span>
|
||||
<span className={`text-sm ${getStatusClass(providerStatus.tvdb)}`}>
|
||||
<span className="mr-2 w-24">TheTVDB:</span>
|
||||
<span
|
||||
className={`text-sm ${getStatusClass(providerStatus.tvdb)}`}
|
||||
data-testid="tvdb-status"
|
||||
>
|
||||
<Badge badgeType={getBadgeType(providerStatus.tvdb)}>
|
||||
{getStatusMessage(providerStatus.tvdb)}
|
||||
</Badge>
|
||||
@@ -254,6 +260,7 @@ const SettingsMetadata = () => {
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<MetadataSelector
|
||||
testId="tv-indexer-selector"
|
||||
value={values.metadata.tv}
|
||||
onChange={(value) => setFieldValue('metadata.tv', value)}
|
||||
isDisabled={isSubmitting}
|
||||
@@ -269,6 +276,7 @@ const SettingsMetadata = () => {
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<MetadataSelector
|
||||
testId="anime-indexer-selector"
|
||||
value={values.metadata.anime}
|
||||
onChange={(value) =>
|
||||
setFieldValue('metadata.anime', value)
|
||||
|
||||
Reference in New Issue
Block a user