Bringing back lint
Some checks failed
Music Collection CI Workflow / test (./backend) (push) Successful in 29s
Music Collection CI Workflow / test (./frontend) (push) Successful in 35s
Music Collection CI Workflow / deploy (push) Has been cancelled
Music Collection CI Workflow / build-and-push-images (./frontend/Dockerfile, git.anatid.net/tabris/music-collection-frontend, ./frontend) (push) Has been cancelled
Music Collection CI Workflow / build-and-push-images (./backend/Dockerfile, git.anatid.net/tabris/music-collection-backend, ./backend) (push) Has been cancelled

This commit is contained in:
Phill Pover 2025-04-07 08:26:20 +01:00
parent 03efd4014e
commit 4996a573dc
21 changed files with 286 additions and 180 deletions

View File

@ -5,7 +5,7 @@ import { AlbumService } from './album.service';
import { Album } from './album.entity'; import { Album } from './album.entity';
import { CreateAlbumDto } from './dto/create-album.dto'; import { CreateAlbumDto } from './dto/create-album.dto';
import { UpdateAlbumDto } from './dto/update-album.dto'; import { UpdateAlbumDto } from './dto/update-album.dto';
import { DeleteResult, Repository } from 'typeorm'; import { DeleteResult } from 'typeorm';
const mockAlbum = new Album(); const mockAlbum = new Album();
const mockCreateAlbumDto = new CreateAlbumDto(); const mockCreateAlbumDto = new CreateAlbumDto();
@ -14,8 +14,6 @@ const mockDeleteResult = new DeleteResult();
describe('AlbumController', () => { describe('AlbumController', () => {
let controller: AlbumController; let controller: AlbumController;
let service: AlbumService;
let repository: Repository<Album>;
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
@ -27,19 +25,27 @@ describe('AlbumController', () => {
useValue: { useValue: {
find: jest.fn().mockResolvedValue([mockAlbum]), find: jest.fn().mockResolvedValue([mockAlbum]),
findOne: jest.fn().mockResolvedValue(mockAlbum), findOne: jest.fn().mockResolvedValue(mockAlbum),
findOneBy: jest.fn().mockResolvedValue({ id: 1, title: 'some album', artist: 'some artist', genre: 'some genre' }), findOneBy: jest.fn().mockResolvedValue({
id: 1,
title: 'some album',
artist: 'some artist',
genre: 'some genre',
}),
create: jest.fn().mockResolvedValue(mockAlbum), create: jest.fn().mockResolvedValue(mockAlbum),
save: jest.fn().mockResolvedValue({ id: 1, title: 'some album', artist: 'some artist', genre: 'some genre' }), save: jest.fn().mockResolvedValue({
id: 1,
title: 'some album',
artist: 'some artist',
genre: 'some genre',
}),
update: jest.fn().mockResolvedValue(mockAlbum), update: jest.fn().mockResolvedValue(mockAlbum),
delete: jest.fn().mockResolvedValue(mockDeleteResult), delete: jest.fn().mockResolvedValue(mockDeleteResult),
}, },
}, },
] ],
}).compile(); }).compile();
controller = module.get<AlbumController>(AlbumController); controller = module.get<AlbumController>(AlbumController);
service = module.get<AlbumService>(AlbumService);
repository = module.get<Repository<Album>>(getRepositoryToken(Album));
}); });
it('should be defined', () => { it('should be defined', () => {
@ -49,31 +55,41 @@ describe('AlbumController', () => {
describe('findAll', () => { describe('findAll', () => {
it('should return an array of Albums', async () => { it('should return an array of Albums', async () => {
expect(await controller.findAll()).toStrictEqual([mockAlbum]); expect(await controller.findAll()).toStrictEqual([mockAlbum]);
}) });
}) });
describe('findOneById', () => { describe('findOneById', () => {
it('should return an Album', async () => { it('should return an Album', async () => {
expect(await controller.findOneById(1)).toStrictEqual(mockAlbum); expect(await controller.findOneById(1)).toStrictEqual(mockAlbum);
}) });
}) });
describe('create', () => { describe('create', () => {
it('should return the Album', async () => { it('should return the Album', async () => {
expect(await controller.create(mockCreateAlbumDto)).toStrictEqual({ id: 1, title: 'some album', artist: 'some artist', genre: 'some genre' }); expect(await controller.create(mockCreateAlbumDto)).toStrictEqual({
}) id: 1,
}) title: 'some album',
artist: 'some artist',
genre: 'some genre',
});
});
});
describe('update', () => { describe('update', () => {
it('should return the Album', async () => { it('should return the Album', async () => {
mockUpdateAlbumDto.id = 1; mockUpdateAlbumDto.id = 1;
expect(await controller.update(1, mockUpdateAlbumDto)).toStrictEqual({ id: 1, title: 'some album', artist: 'some artist', genre: 'some genre' }); expect(await controller.update(1, mockUpdateAlbumDto)).toStrictEqual({
}) id: 1,
}) title: 'some album',
artist: 'some artist',
genre: 'some genre',
});
});
});
describe('remove', () => { describe('remove', () => {
it('should return a DeleteResult', async () => { it('should return a DeleteResult', async () => {
expect(await controller.remove(1)).toStrictEqual(mockDeleteResult); expect(await controller.remove(1)).toStrictEqual(mockDeleteResult);
}) });
}) });
}); });

View File

@ -1,4 +1,14 @@
import { Body, Controller, Delete, Get, Param, Post, Put, UsePipes, ValidationPipe } from '@nestjs/common'; import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { DeleteResult } from 'typeorm'; import { DeleteResult } from 'typeorm';
import { AlbumService } from './album.service'; import { AlbumService } from './album.service';
import { Album } from './album.entity'; import { Album } from './album.entity';
@ -7,7 +17,6 @@ import { UpdateAlbumDto } from './dto/update-album.dto';
@Controller('album') @Controller('album')
export class AlbumController { export class AlbumController {
constructor(private readonly albumService: AlbumService) {} constructor(private readonly albumService: AlbumService) {}
@Get() @Get()
@ -28,7 +37,10 @@ export class AlbumController {
@Put(':id') @Put(':id')
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))
async update(@Param('id') id: number, @Body() updateAlbumDto: UpdateAlbumDto): Promise<Album | null> { async update(
@Param('id') id: number,
@Body() updateAlbumDto: UpdateAlbumDto,
): Promise<Album | null> {
return this.albumService.update(id, updateAlbumDto); return this.albumService.update(id, updateAlbumDto);
} }

View File

@ -2,26 +2,29 @@ import { Entity, Column, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { IsNotEmpty, IsString } from 'class-validator'; import { IsNotEmpty, IsString } from 'class-validator';
import { Song } from '../song/song.entity'; import { Song } from '../song/song.entity';
@Entity("album") @Entity('album')
export class Album { export class Album {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number id: number;
@Column({ unique: true }) @Column({ unique: true })
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
title: string title: string;
@Column() @Column()
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
artist: string artist: string;
@Column() @Column()
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
genre: string genre: string;
@OneToMany(() => Song, (song) => song.album, { eager: true, onDelete: 'CASCADE' }) @OneToMany(() => Song, (song) => song.album, {
songs: Song[] eager: true,
onDelete: 'CASCADE',
})
songs: Song[];
} }

View File

@ -19,6 +19,6 @@ import { APP_PIPE } from '@nestjs/core';
}), }),
}, },
], ],
exports: [TypeOrmModule] exports: [TypeOrmModule],
}) })
export class AlbumModule {} export class AlbumModule {}

View File

@ -4,7 +4,7 @@ import { AlbumService } from './album.service';
import { Album } from './album.entity'; import { Album } from './album.entity';
import { CreateAlbumDto } from './dto/create-album.dto'; import { CreateAlbumDto } from './dto/create-album.dto';
import { UpdateAlbumDto } from './dto/update-album.dto'; import { UpdateAlbumDto } from './dto/update-album.dto';
import { DeleteResult, Repository } from 'typeorm'; import { DeleteResult } from 'typeorm';
const mockAlbum = new Album(); const mockAlbum = new Album();
const mockCreateAlbumDto = new CreateAlbumDto(); const mockCreateAlbumDto = new CreateAlbumDto();
@ -13,7 +13,6 @@ const mockDeleteResult = new DeleteResult();
describe('AlbumService', () => { describe('AlbumService', () => {
let service: AlbumService; let service: AlbumService;
let repository: Repository<Album>;
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
@ -24,18 +23,32 @@ describe('AlbumService', () => {
useValue: { useValue: {
find: jest.fn().mockResolvedValue([mockAlbum]), find: jest.fn().mockResolvedValue([mockAlbum]),
findOne: jest.fn().mockResolvedValue(mockAlbum), findOne: jest.fn().mockResolvedValue(mockAlbum),
findOneBy: jest.fn().mockResolvedValue({ id: 1, title: 'some album', artist: 'some artist', genre: 'some genre' }), findOneBy: jest.fn().mockResolvedValue({
id: 1,
title: 'some album',
artist: 'some artist',
genre: 'some genre',
}),
create: jest.fn().mockResolvedValue(mockAlbum), create: jest.fn().mockResolvedValue(mockAlbum),
save: jest.fn().mockResolvedValue({ id: 1, title: 'some album', artist: 'some artist', genre: 'some genre' }), save: jest.fn().mockResolvedValue({
update: jest.fn().mockResolvedValue({ id: 1, title: 'some album', artist: 'some artist', genre: 'some genre' }), id: 1,
title: 'some album',
artist: 'some artist',
genre: 'some genre',
}),
update: jest.fn().mockResolvedValue({
id: 1,
title: 'some album',
artist: 'some artist',
genre: 'some genre',
}),
delete: jest.fn().mockResolvedValue(mockDeleteResult), delete: jest.fn().mockResolvedValue(mockDeleteResult),
}, },
}, },
] ],
}).compile(); }).compile();
service = module.get<AlbumService>(AlbumService); service = module.get<AlbumService>(AlbumService);
repository = module.get<Repository<Album>>(getRepositoryToken(Album));
}); });
it('should be defined', () => { it('should be defined', () => {
@ -45,31 +58,41 @@ describe('AlbumService', () => {
describe('findAll', () => { describe('findAll', () => {
it('should return an array of Albums', async () => { it('should return an array of Albums', async () => {
expect(await service.findAll()).toStrictEqual([mockAlbum]); expect(await service.findAll()).toStrictEqual([mockAlbum]);
}) });
}) });
describe('findOneById', () => { describe('findOneById', () => {
it('should return an Album', async () => { it('should return an Album', async () => {
expect(await service.findOneById(1)).toStrictEqual(mockAlbum); expect(await service.findOneById(1)).toStrictEqual(mockAlbum);
}) });
}) });
describe('create', () => { describe('create', () => {
it('should return the Album', async () => { it('should return the Album', async () => {
expect(await service.create(mockCreateAlbumDto)).toStrictEqual({ id: 1, title: 'some album', artist: 'some artist', genre: 'some genre' }); expect(await service.create(mockCreateAlbumDto)).toStrictEqual({
}) id: 1,
}) title: 'some album',
artist: 'some artist',
genre: 'some genre',
});
});
});
describe('update', () => { describe('update', () => {
it('should return the Album', async () => { it('should return the Album', async () => {
mockUpdateAlbumDto.id = 1; mockUpdateAlbumDto.id = 1;
expect(await service.update(1, mockUpdateAlbumDto)).toStrictEqual({ id: 1, title: 'some album', artist: 'some artist', genre: 'some genre' }); expect(await service.update(1, mockUpdateAlbumDto)).toStrictEqual({
}) id: 1,
}) title: 'some album',
artist: 'some artist',
genre: 'some genre',
});
});
});
describe('remove', () => { describe('remove', () => {
it('should return a DeleteResult', async () => { it('should return a DeleteResult', async () => {
expect(await service.remove(1)).toStrictEqual(mockDeleteResult); expect(await service.remove(1)).toStrictEqual(mockDeleteResult);
}) });
}) });
}); });

View File

@ -9,31 +9,31 @@ import { UpdateAlbumDto } from './dto/update-album.dto';
export class AlbumService { export class AlbumService {
constructor( constructor(
@InjectRepository(Album) @InjectRepository(Album)
private albumRepository: Repository<Album> private albumRepository: Repository<Album>,
) {} ) {}
findAll(): Promise<Album[]> { findAll(): Promise<Album[]> {
return this.albumRepository.find({ return this.albumRepository.find({
order: { order: {
artist: "ASC", artist: 'ASC',
title: "ASC" title: 'ASC',
} },
}); });
} }
findOneById(id: number): Promise<Album | null> { findOneById(id: number): Promise<Album | null> {
return this.albumRepository.findOne({ return this.albumRepository.findOne({
where: { where: {
id: id id: id,
}, },
relations: { relations: {
songs: true songs: true,
}, },
order: { order: {
songs: { songs: {
trackNumber: "ASC" trackNumber: 'ASC',
}, },
} },
}); });
} }
@ -41,29 +41,43 @@ export class AlbumService {
const album = this.albumRepository.create({ const album = this.albumRepository.create({
title: createAlbumDto.title, title: createAlbumDto.title,
artist: createAlbumDto.artist, artist: createAlbumDto.artist,
genre: createAlbumDto.genre genre: createAlbumDto.genre,
}); });
const savedAlbum = await this.albumRepository.save(album); const savedAlbum = await this.albumRepository.save(album);
return savedAlbum; return savedAlbum;
} }
async update(id: number, updateAlbumDto: UpdateAlbumDto): Promise<Album | null> { async update(
id: number,
updateAlbumDto: UpdateAlbumDto,
): Promise<Album | null> {
if (id == updateAlbumDto.id) { if (id == updateAlbumDto.id) {
const albumToUpdate = await this.albumRepository.findOneBy({ id: updateAlbumDto.id }); const albumToUpdate = await this.albumRepository.findOneBy({
id: updateAlbumDto.id,
});
if (!albumToUpdate) { if (!albumToUpdate) {
console.error("AlbumService: update: Didn't find album: ", id); console.error("AlbumService: update: Didn't find album: ", id);
return null; return null;
} }
await this.albumRepository.update({ id: updateAlbumDto.id }, { await this.albumRepository.update(
title: updateAlbumDto.title, { id: updateAlbumDto.id },
artist: updateAlbumDto.artist, {
genre: updateAlbumDto.genre title: updateAlbumDto.title,
artist: updateAlbumDto.artist,
genre: updateAlbumDto.genre,
},
);
const album = await this.albumRepository.findOneBy({
id: updateAlbumDto.id,
}); });
const album = await this.albumRepository.findOneBy({ id: updateAlbumDto.id });
return album; return album;
} else { } else {
console.error("AlbumService: update: IDs do not match", id, updateAlbumDto); console.error(
'AlbumService: update: IDs do not match',
id,
updateAlbumDto,
);
return null; return null;
} }
} }

View File

@ -10,7 +10,7 @@ import { SongModule } from '@/song/song.module';
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot({ ConfigModule.forRoot({
load: [configuration] load: [configuration],
}), }),
DatabaseModule, DatabaseModule,
AlbumModule, AlbumModule,

View File

@ -5,6 +5,6 @@ export default () => ({
port: parseInt(process.env.POSTGRES_PORT!, 10) || 5432, port: parseInt(process.env.POSTGRES_PORT!, 10) || 5432,
name: process.env.DB_NAME || 'music-collection', name: process.env.DB_NAME || 'music-collection',
user: process.env.DB_USER || 'music-collection', user: process.env.DB_USER || 'music-collection',
password: process.env.DB_PASSWORD password: process.env.DB_PASSWORD,
} },
}); });

View File

@ -6,7 +6,7 @@ import configuration from '../config/configuration';
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot({ ConfigModule.forRoot({
load: [configuration] load: [configuration],
}), }),
TypeOrmModule.forRootAsync({ TypeOrmModule.forRootAsync({
imports: [ConfigModule], imports: [ConfigModule],

View File

@ -10,9 +10,10 @@ async function bootstrap() {
origin: ['https://music.anatid.net'], origin: ['https://music.anatid.net'],
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true, credentials: true,
allowedHeaders: 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept, Observe', allowedHeaders:
'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept, Observe',
}); });
app.useGlobalPipes(new ValidationPipe({transform: true})); app.useGlobalPipes(new ValidationPipe({ transform: true }));
await app.listen(process.env.PORT ?? 3000); await app.listen(process.env.PORT ?? 3000);
} }
bootstrap(); await bootstrap();

View File

@ -4,14 +4,10 @@ import { SongController } from './song.controller';
import { SongService } from './song.service'; import { SongService } from './song.service';
import { Song } from './song.entity'; import { Song } from './song.entity';
import { Album } from '../album/album.entity'; import { Album } from '../album/album.entity';
import { CreateSongDto } from './dto/create-song.dto'; import { DeleteResult } from 'typeorm';
import { UpdateSongDto } from './dto/update-song.dto';
import { DeleteResult, Repository } from 'typeorm';
const mockSong = new Song(); const mockSong = new Song();
const mockAlbum = new Album(); const mockAlbum = new Album();
const mockCreateSongDto = new CreateSongDto();
const mockUpdateSongDto = new UpdateSongDto();
const mockDeleteResult = new DeleteResult(); const mockDeleteResult = new DeleteResult();
describe('SongController', () => { describe('SongController', () => {
@ -27,9 +23,21 @@ describe('SongController', () => {
useValue: { useValue: {
find: jest.fn().mockResolvedValue([mockSong]), find: jest.fn().mockResolvedValue([mockSong]),
findOne: jest.fn().mockResolvedValue(mockSong), findOne: jest.fn().mockResolvedValue(mockSong),
findOneBy: jest.fn().mockResolvedValue({ id: 3, title: 'some song', duration: 300, trackNumber: 2, album: mockAlbum }), findOneBy: jest.fn().mockResolvedValue({
id: 3,
title: 'some song',
duration: 300,
trackNumber: 2,
album: mockAlbum,
}),
create: jest.fn().mockResolvedValue(mockSong), create: jest.fn().mockResolvedValue(mockSong),
save: jest.fn().mockResolvedValue({ id: 3, title: 'some song', duration: 300, trackNumber: 2, album: mockAlbum }), save: jest.fn().mockResolvedValue({
id: 3,
title: 'some song',
duration: 300,
trackNumber: 2,
album: mockAlbum,
}),
update: jest.fn().mockResolvedValue(mockSong), update: jest.fn().mockResolvedValue(mockSong),
delete: jest.fn().mockResolvedValue(mockDeleteResult), delete: jest.fn().mockResolvedValue(mockDeleteResult),
}, },
@ -39,14 +47,24 @@ describe('SongController', () => {
useValue: { useValue: {
find: jest.fn().mockResolvedValue([mockAlbum]), find: jest.fn().mockResolvedValue([mockAlbum]),
findOne: jest.fn().mockResolvedValue(mockAlbum), findOne: jest.fn().mockResolvedValue(mockAlbum),
findOneBy: jest.fn().mockResolvedValue({ id: 1, title: 'some album', artist: 'some artist', genre: 'some genre' }), findOneBy: jest.fn().mockResolvedValue({
id: 1,
title: 'some album',
artist: 'some artist',
genre: 'some genre',
}),
create: jest.fn().mockResolvedValue(mockAlbum), create: jest.fn().mockResolvedValue(mockAlbum),
save: jest.fn().mockResolvedValue({ id: 1, title: 'some album', artist: 'some artist', genre: 'some genre' }), save: jest.fn().mockResolvedValue({
id: 1,
title: 'some album',
artist: 'some artist',
genre: 'some genre',
}),
update: jest.fn().mockResolvedValue(mockAlbum), update: jest.fn().mockResolvedValue(mockAlbum),
delete: jest.fn().mockResolvedValue(mockDeleteResult), delete: jest.fn().mockResolvedValue(mockDeleteResult),
}, },
}, },
] ],
}).compile(); }).compile();
controller = module.get<SongController>(SongController); controller = module.get<SongController>(SongController);

View File

@ -1,4 +1,14 @@
import { Body, Controller, Delete, Get, Param, Post, Put, UsePipes, ValidationPipe } from '@nestjs/common'; import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { DeleteResult } from 'typeorm'; import { DeleteResult } from 'typeorm';
import { SongService } from './song.service'; import { SongService } from './song.service';
import { Song } from './song.entity'; import { Song } from './song.entity';
@ -7,7 +17,6 @@ import { UpdateSongDto } from './dto/update-song.dto';
@Controller('song') @Controller('song')
export class SongController { export class SongController {
constructor(private readonly songService: SongService) {} constructor(private readonly songService: SongService) {}
@Get() @Get()
@ -28,7 +37,10 @@ export class SongController {
@Put(':id') @Put(':id')
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))
async update(@Param('id') id: number, @Body() updateSongDto: UpdateSongDto): Promise<Song | null> { async update(
@Param('id') id: number,
@Body() updateSongDto: UpdateSongDto,
): Promise<Song | null> {
console.log(updateSongDto); console.log(updateSongDto);
return this.songService.update(id, updateSongDto); return this.songService.update(id, updateSongDto);
} }

View File

@ -2,24 +2,24 @@ import { Entity, Column, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
import { Album } from '../album/album.entity'; import { Album } from '../album/album.entity';
@Entity("song") @Entity('song')
export class Song { export class Song {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number id: number;
@Column({ unique: true }) @Column({ unique: true })
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
title: string title: string;
@Column() @Column()
@IsNumber() @IsNumber()
duration: number duration: number;
@Column() @Column()
@IsNumber() @IsNumber()
trackNumber: number trackNumber: number;
@ManyToOne(() => Album, (album) => album.songs, { onDelete: 'CASCADE' }) @ManyToOne(() => Album, (album) => album.songs, { onDelete: 'CASCADE' })
album: Album album: Album;
} }

View File

@ -7,10 +7,7 @@ import { SongService } from './song.service';
import { APP_PIPE } from '@nestjs/core'; import { APP_PIPE } from '@nestjs/core';
@Module({ @Module({
imports: [ imports: [TypeOrmModule.forFeature([Song]), AlbumModule],
TypeOrmModule.forFeature([Song]),
AlbumModule
],
controllers: [SongController], controllers: [SongController],
providers: [ providers: [
SongService, SongService,
@ -23,6 +20,6 @@ import { APP_PIPE } from '@nestjs/core';
}), }),
}, },
], ],
exports: [TypeOrmModule] exports: [TypeOrmModule],
}) })
export class SongModule {} export class SongModule {}

View File

@ -20,7 +20,7 @@ describe('SongService', () => {
findAll: jest.fn().mockResolvedValue([mockSong]), findAll: jest.fn().mockResolvedValue([mockSong]),
findOneById: jest.fn().mockResolvedValue(mockSong), findOneById: jest.fn().mockResolvedValue(mockSong),
create: jest.fn().mockResolvedValue(1), create: jest.fn().mockResolvedValue(1),
update: jest.fn().mockResolvedValue("Song updated successfully"), update: jest.fn().mockResolvedValue('Song updated successfully'),
remove: jest.fn().mockResolvedValue(null), remove: jest.fn().mockResolvedValue(null),
}, },
}, },
@ -30,11 +30,11 @@ describe('SongService', () => {
findAll: jest.fn().mockResolvedValue([mockAlbum]), findAll: jest.fn().mockResolvedValue([mockAlbum]),
findOneById: jest.fn().mockResolvedValue(mockAlbum), findOneById: jest.fn().mockResolvedValue(mockAlbum),
create: jest.fn().mockResolvedValue(1), create: jest.fn().mockResolvedValue(1),
update: jest.fn().mockResolvedValue("Album updated successfully"), update: jest.fn().mockResolvedValue('Album updated successfully'),
remove: jest.fn().mockResolvedValue(null), remove: jest.fn().mockResolvedValue(null),
}, },
}, },
] ],
}).compile(); }).compile();
service = module.get<SongService>(SongService); service = module.get<SongService>(SongService);

View File

@ -12,14 +12,14 @@ export class SongService {
@InjectRepository(Song) @InjectRepository(Song)
private songRepository: Repository<Song>, private songRepository: Repository<Song>,
@InjectRepository(Album) @InjectRepository(Album)
private albumRepository: Repository<Album> private albumRepository: Repository<Album>,
) {} ) {}
findAll(): Promise<Song[]> { findAll(): Promise<Song[]> {
return this.songRepository.find({ return this.songRepository.find({
order: { order: {
trackNumber: "ASC" trackNumber: 'ASC',
} },
}); });
} }
@ -28,7 +28,9 @@ export class SongService {
} }
async create(createSongDto: CreateSongDto): Promise<Song | null> { async create(createSongDto: CreateSongDto): Promise<Song | null> {
const album = await this.albumRepository.findOneBy({id: createSongDto.albumId}); const album = await this.albumRepository.findOneBy({
id: createSongDto.albumId,
});
if (album) { if (album) {
const song = new Song(); const song = new Song();
song.title = createSongDto.title; song.title = createSongDto.title;
@ -44,23 +46,32 @@ export class SongService {
async update(id: number, updateSongDto: UpdateSongDto): Promise<Song | null> { async update(id: number, updateSongDto: UpdateSongDto): Promise<Song | null> {
if (id == updateSongDto.id) { if (id == updateSongDto.id) {
const album = await this.albumRepository.findOneBy({id: updateSongDto.albumId}); const album = await this.albumRepository.findOneBy({
const songToUpdate = await this.songRepository.findOneBy({ id: updateSongDto.id }); id: updateSongDto.albumId,
});
const songToUpdate = await this.songRepository.findOneBy({
id: updateSongDto.id,
});
if (!songToUpdate || !album) { if (!songToUpdate || !album) {
console.error("SongService: update: Song or Album not found"); console.error('SongService: update: Song or Album not found');
return null; return null;
} }
await this.songRepository.update({ id: updateSongDto.id }, { await this.songRepository.update(
title: updateSongDto.title, { id: updateSongDto.id },
duration: updateSongDto.duration, {
trackNumber: updateSongDto.trackNumber, title: updateSongDto.title,
album: album, duration: updateSongDto.duration,
trackNumber: updateSongDto.trackNumber,
album: album,
},
);
const song = await this.songRepository.findOneBy({
id: updateSongDto.id,
}); });
const song = await this.songRepository.findOneBy({ id: updateSongDto.id });
return song; return song;
} else { } else {
console.error("SongService: update: IDs do not match"); console.error('SongService: update: IDs do not match');
return null; return null;
} }
} }
@ -68,5 +79,4 @@ export class SongService {
async remove(id: number): Promise<DeleteResult> { async remove(id: number): Promise<DeleteResult> {
return await this.songRepository.delete(id); return await this.songRepository.delete(id);
} }
} }

View File

@ -6,7 +6,7 @@
"dev": "next dev --turbopack", "dev": "next dev --turbopack",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint --fix",
"test": "jest --passWithNoTests" "test": "jest --passWithNoTests"
}, },
"dependencies": { "dependencies": {