-
Notifications
You must be signed in to change notification settings - Fork 4
Add tests and renovate configuration for renovate demo #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 6 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
f9969d5
feat: add Jest v29 test suite with deprecated matchers for Renovate demo
loujaybee d81ed8d
feat: add Renovate CLI integration with flexible filtering
loujaybee 536df45
feat: add Renovate CLI as DevContainer feature
loujaybee 39f6cc0
Merge main branch and resolve renovate.json conflict
loujaybee f8f8201
feat: add Jest-specific renovate command
loujaybee fa46f24
feat: working Jest-only renovate command
loujaybee 75e02a2
small fixes
loujaybee cb6a241
Simplify renovate commands and add demo instructions
loujaybee 19c1770
Configure renovate to always create Jest PR and exclude other dependeβ¦
loujaybee 7c251ec
Force Jest v30+ updates only and always recreate PRs
loujaybee 711a840
Simplify renovate config to only handle Jest dependencies
loujaybee 581f128
Use recreateClosed instead of recreateWhen for Jest PRs
loujaybee b84705a
Use packageRules to explicitly enable only Jest and recreate closed PRs
loujaybee 8d86144
Remove npm script and update demo docs to use renovate CLI directly
loujaybee 27092f7
Add GitHub Action for catalog service tests
loujaybee feae5ac
Fix JSON syntax error in package.json
loujaybee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module.exports = { | ||
preset: 'ts-jest', | ||
testEnvironment: 'node', | ||
testTimeout: 5000, | ||
forceExit: true, | ||
verbose: true, | ||
bail: true | ||
}; |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
backend/catalog/src/__tests__/deprecated-matchers.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
// This test file uses Jest v29 deprecated matcher syntax that will break in v30 | ||
describe('Jest v29 Deprecated Matchers Demo', () => { | ||
describe('Mock function matchers that will break in Jest v30', () => { | ||
it('uses toBeCalled instead of toHaveBeenCalled', () => { | ||
const mockFn = jest.fn(); | ||
mockFn('test'); | ||
|
||
// This will break in Jest v30 - should be toHaveBeenCalled() | ||
expect(mockFn).toBeCalled(); | ||
}); | ||
|
||
it('uses toBeCalledTimes instead of toHaveBeenCalledTimes', () => { | ||
const mockFn = jest.fn(); | ||
mockFn('first'); | ||
mockFn('second'); | ||
|
||
// This will break in Jest v30 - should be toHaveBeenCalledTimes() | ||
expect(mockFn).toBeCalledTimes(2); | ||
}); | ||
|
||
it('uses toBeCalledWith instead of toHaveBeenCalledWith', () => { | ||
const mockFn = jest.fn(); | ||
mockFn('test-arg'); | ||
|
||
// This will break in Jest v30 - should be toHaveBeenCalledWith() | ||
expect(mockFn).toBeCalledWith('test-arg'); | ||
}); | ||
|
||
it('uses lastCalledWith instead of toHaveBeenLastCalledWith', () => { | ||
const mockFn = jest.fn(); | ||
mockFn('first'); | ||
mockFn('last'); | ||
|
||
// This will break in Jest v30 - should be toHaveBeenLastCalledWith() | ||
expect(mockFn).lastCalledWith('last'); | ||
}); | ||
|
||
it('uses nthCalledWith instead of toHaveBeenNthCalledWith', () => { | ||
const mockFn = jest.fn(); | ||
mockFn('first'); | ||
mockFn('second'); | ||
|
||
// This will break in Jest v30 - should be toHaveBeenNthCalledWith() | ||
expect(mockFn).nthCalledWith(1, 'first'); | ||
expect(mockFn).nthCalledWith(2, 'second'); | ||
}); | ||
}); | ||
|
||
describe('Return value matchers that will break in Jest v30', () => { | ||
it('uses toReturn instead of toHaveReturned', () => { | ||
const mockFn = jest.fn().mockReturnValue('result'); | ||
mockFn(); | ||
|
||
// This will break in Jest v30 - should be toHaveReturned() | ||
expect(mockFn).toReturn(); | ||
}); | ||
|
||
it('uses toReturnTimes instead of toHaveReturnedTimes', () => { | ||
const mockFn = jest.fn().mockReturnValue('result'); | ||
mockFn(); | ||
mockFn(); | ||
|
||
// This will break in Jest v30 - should be toHaveReturnedTimes() | ||
expect(mockFn).toReturnTimes(2); | ||
}); | ||
|
||
it('uses toReturnWith instead of toHaveReturnedWith', () => { | ||
const mockFn = jest.fn().mockReturnValue('specific-result'); | ||
mockFn(); | ||
|
||
// This will break in Jest v30 - should be toHaveReturnedWith() | ||
expect(mockFn).toReturnWith('specific-result'); | ||
}); | ||
|
||
it('uses lastReturnedWith instead of toHaveLastReturnedWith', () => { | ||
const mockFn = jest.fn(); | ||
mockFn.mockReturnValueOnce('first'); | ||
mockFn.mockReturnValueOnce('last'); | ||
mockFn(); | ||
mockFn(); | ||
|
||
// This will break in Jest v30 - should be toHaveLastReturnedWith() | ||
expect(mockFn).lastReturnedWith('last'); | ||
}); | ||
|
||
it('uses nthReturnedWith instead of toHaveNthReturnedWith', () => { | ||
const mockFn = jest.fn(); | ||
mockFn.mockReturnValueOnce('first'); | ||
mockFn.mockReturnValueOnce('second'); | ||
mockFn(); | ||
mockFn(); | ||
|
||
// This will break in Jest v30 - should be toHaveNthReturnedWith() | ||
expect(mockFn).nthReturnedWith(1, 'first'); | ||
expect(mockFn).nthReturnedWith(2, 'second'); | ||
}); | ||
}); | ||
|
||
describe('Error matchers that will break in Jest v30', () => { | ||
it('uses toThrowError instead of toThrow', () => { | ||
const errorFn = () => { | ||
throw new Error('Test error'); | ||
}; | ||
|
||
// This will break in Jest v30 - should be toThrow() | ||
expect(errorFn).toThrowError('Test error'); | ||
}); | ||
|
||
it('uses toThrowError with no message instead of toThrow', () => { | ||
const errorFn = () => { | ||
throw new Error('Any error'); | ||
}; | ||
|
||
// This will break in Jest v30 - should be toThrow() | ||
expect(errorFn).toThrowError(); | ||
}); | ||
}); | ||
}); | ||
loujaybee marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { formatMovieTitle, calculateAverageRating } from '../utils/movieUtils'; | ||
|
||
describe('Movie Utilities', () => { | ||
describe('formatMovieTitle', () => { | ||
it('should format title with proper capitalization', () => { | ||
expect(formatMovieTitle('the dark knight')).toBe('The Dark Knight'); | ||
expect(formatMovieTitle('PULP FICTION')).toBe('Pulp Fiction'); | ||
expect(formatMovieTitle('fight club')).toBe('Fight Club'); | ||
}); | ||
|
||
it('should handle empty strings', () => { | ||
expect(formatMovieTitle('')).toBe(''); | ||
}); | ||
|
||
it('should handle single words', () => { | ||
expect(formatMovieTitle('matrix')).toBe('Matrix'); | ||
expect(formatMovieTitle('MATRIX')).toBe('Matrix'); | ||
}); | ||
}); | ||
|
||
describe('calculateAverageRating', () => { | ||
it('should calculate correct average rating', () => { | ||
const movies = [ | ||
{ title: 'Movie 1', description: 'Desc 1', release_year: 2000, rating: 8.0, image_url: 'url1' }, | ||
{ title: 'Movie 2', description: 'Desc 2', release_year: 2001, rating: 9.0, image_url: 'url2' }, | ||
{ title: 'Movie 3', description: 'Desc 3', release_year: 2002, rating: 7.0, image_url: 'url3' } | ||
]; | ||
|
||
expect(calculateAverageRating(movies)).toBe(8.0); | ||
}); | ||
|
||
it('should return 0 for empty array', () => { | ||
expect(calculateAverageRating([])).toBe(0); | ||
}); | ||
|
||
it('should handle single movie', () => { | ||
const movies = [ | ||
{ title: 'Solo Movie', description: 'Desc', release_year: 2000, rating: 7.5, image_url: 'url' } | ||
]; | ||
|
||
expect(calculateAverageRating(movies)).toBe(7.5); | ||
}); | ||
}); | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { Pool } from 'pg'; | ||
import { Movie } from '../utils/movieUtils'; | ||
|
||
export class CatalogService { | ||
private pool: Pool; | ||
|
||
constructor(pool: Pool) { | ||
this.pool = pool; | ||
} | ||
|
||
async getAllMovies(): Promise<Movie[]> { | ||
const result = await this.pool.query('SELECT * FROM movies ORDER BY rating DESC'); | ||
return result.rows; | ||
} | ||
|
||
async getMovieById(id: number): Promise<Movie | null> { | ||
const result = await this.pool.query('SELECT * FROM movies WHERE id = $1', [id]); | ||
return result.rows[0] || null; | ||
} | ||
|
||
async searchMovies(query: string): Promise<Movie[]> { | ||
const searchQuery = `%${query.toLowerCase()}%`; | ||
const result = await this.pool.query( | ||
'SELECT * FROM movies WHERE LOWER(title) LIKE $1 OR LOWER(description) LIKE $1', | ||
[searchQuery] | ||
); | ||
return result.rows; | ||
} | ||
|
||
async getTopRatedMovies(limit: number = 10): Promise<Movie[]> { | ||
const result = await this.pool.query( | ||
'SELECT * FROM movies ORDER BY rating DESC LIMIT $1', | ||
[limit] | ||
); | ||
return result.rows; | ||
} | ||
|
||
async getMoviesByYear(year: number): Promise<Movie[]> { | ||
const result = await this.pool.query( | ||
'SELECT * FROM movies WHERE release_year = $1 ORDER BY rating DESC', | ||
[year] | ||
); | ||
return result.rows; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
export interface ProcessingResult<T> { | ||
data: T[]; | ||
processed: number; | ||
errors: string[]; | ||
} | ||
|
||
export function processMovieData<T>( | ||
items: T[], | ||
processor: (item: T) => T | null | ||
): ProcessingResult<T> { | ||
const result: ProcessingResult<T> = { | ||
data: [], | ||
processed: 0, | ||
errors: [] | ||
}; | ||
|
||
for (const item of items) { | ||
try { | ||
const processed = processor(item); | ||
if (processed !== null) { | ||
result.data.push(processed); | ||
result.processed++; | ||
} | ||
} catch (error) { | ||
result.errors.push(error instanceof Error ? error.message : 'Unknown error'); | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
export function batchProcess<T, R>( | ||
items: T[], | ||
batchSize: number, | ||
processor: (batch: T[]) => Promise<R[]> | ||
): Promise<R[]> { | ||
const batches: T[][] = []; | ||
|
||
for (let i = 0; i < items.length; i += batchSize) { | ||
batches.push(items.slice(i, i + batchSize)); | ||
} | ||
|
||
return Promise.all(batches.map(processor)).then(results => | ||
results.flat() | ||
); | ||
} | ||
|
||
export function sanitizeInput(input: string): string { | ||
return input | ||
.trim() | ||
.replace(/<[^>]*>/g, '') | ||
.replace(/script/gi, '') | ||
.substring(0, 1000); | ||
} | ||
|
||
export function parseRating(rating: string | number): number { | ||
if (typeof rating === 'number') { | ||
return Math.max(0, Math.min(10, rating)); | ||
} | ||
|
||
const parsed = parseFloat(rating); | ||
if (isNaN(parsed)) { | ||
return 0; | ||
} | ||
|
||
return Math.max(0, Math.min(10, parsed)); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
export interface Movie { | ||
id?: number; | ||
title: string; | ||
description: string; | ||
release_year: number; | ||
rating: number; | ||
image_url: string; | ||
} | ||
|
||
export function validateMovie(movie: Partial<Movie>): string[] { | ||
const errors: string[] = []; | ||
|
||
if (!movie.title || movie.title.trim().length === 0) { | ||
errors.push('Title is required'); | ||
} | ||
|
||
if (!movie.description || movie.description.trim().length === 0) { | ||
errors.push('Description is required'); | ||
} | ||
|
||
if (!movie.release_year || movie.release_year < 1900 || movie.release_year > new Date().getFullYear()) { | ||
errors.push('Release year must be between 1900 and current year'); | ||
} | ||
|
||
if (!movie.rating || movie.rating < 0 || movie.rating > 10) { | ||
errors.push('Rating must be between 0 and 10'); | ||
} | ||
|
||
if (!movie.image_url || !isValidUrl(movie.image_url)) { | ||
errors.push('Valid image URL is required'); | ||
} | ||
|
||
return errors; | ||
} | ||
|
||
export function isValidUrl(url: string): boolean { | ||
try { | ||
new URL(url); | ||
return true; | ||
} catch { | ||
return false; | ||
} | ||
} | ||
|
||
export function formatMovieTitle(title: string): string { | ||
return title | ||
.split(' ') | ||
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) | ||
.join(' '); | ||
} | ||
|
||
export function calculateAverageRating(movies: Movie[]): number { | ||
if (movies.length === 0) return 0; | ||
|
||
const sum = movies.reduce((acc, movie) => acc + movie.rating, 0); | ||
return Math.round((sum / movies.length) * 10) / 10; | ||
} | ||
|
||
export function filterMoviesByDecade(movies: Movie[], decade: number): Movie[] { | ||
const startYear = decade; | ||
const endYear = decade + 9; | ||
|
||
return movies.filter(movie => | ||
movie.release_year >= startYear && movie.release_year <= endYear | ||
); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move commands to actual configuration file