Skip to content

Commit 71f73f1

Browse files
committed
Merge branch 'develop' of github.com:OpenNBS/NoteBlockWorld into develop
2 parents c332eb5 + 4c2d780 commit 71f73f1

File tree

9 files changed

+113
-46
lines changed

9 files changed

+113
-46
lines changed

apps/backend/src/auth/auth.controller.spec.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ describe('AuthController', () => {
2222
let authService: AuthService;
2323

2424
beforeEach(async () => {
25+
// Clear all mocks before each test to ensure test isolation
26+
jest.clearAllMocks();
27+
2528
const module: TestingModule = await Test.createTestingModule({
2629
controllers: [AuthController],
2730
providers: [
@@ -68,8 +71,13 @@ describe('AuthController', () => {
6871

6972
describe('githubLogin', () => {
7073
it('should call AuthService.githubLogin', async () => {
71-
await controller.githubLogin();
72-
expect(authService.githubLogin).toHaveBeenCalled();
74+
// githubLogin is just a Passport guard entry point - it doesn't call authService
75+
// The actual login is handled by the callback endpoint (githubRedirect)
76+
controller.githubLogin();
77+
// Verify the method exists and can be called without errors
78+
expect(controller.githubLogin).toBeDefined();
79+
// Verify authService was NOT called (since this is just a guard entry point)
80+
expect(authService.githubLogin).not.toHaveBeenCalled();
7381
});
7482
});
7583

@@ -97,8 +105,13 @@ describe('AuthController', () => {
97105

98106
describe('googleLogin', () => {
99107
it('should call AuthService.googleLogin', async () => {
100-
await controller.googleLogin();
101-
expect(authService.googleLogin).toHaveBeenCalled();
108+
// googleLogin is just a Passport guard entry point - it doesn't call authService
109+
// The actual login is handled by the callback endpoint (googleRedirect)
110+
controller.googleLogin();
111+
// Verify the method exists and can be called without errors
112+
expect(controller.googleLogin).toBeDefined();
113+
// Verify authService was NOT called (since this is just a guard entry point)
114+
expect(authService.googleLogin).not.toHaveBeenCalled();
102115
});
103116
});
104117

@@ -126,8 +139,13 @@ describe('AuthController', () => {
126139

127140
describe('discordLogin', () => {
128141
it('should call AuthService.discordLogin', async () => {
129-
await controller.discordLogin();
130-
expect(authService.discordLogin).toHaveBeenCalled();
142+
// discordLogin is just a Passport guard entry point - it doesn't call authService
143+
// The actual login is handled by the callback endpoint (discordRedirect)
144+
controller.discordLogin();
145+
// Verify the method exists and can be called without errors
146+
expect(controller.discordLogin).toBeDefined();
147+
// Verify authService was NOT called (since this is just a guard entry point)
148+
expect(authService.discordLogin).not.toHaveBeenCalled();
131149
});
132150
});
133151

apps/backend/src/song/my-songs/my-songs.controller.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
1+
import { Controller, Get, Inject, Query, UseGuards } from '@nestjs/common';
22
import { AuthGuard } from '@nestjs/passport';
33
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
44

@@ -12,7 +12,10 @@ import { SongService } from '../song.service';
1212
@Controller('my-songs')
1313
@ApiTags('song')
1414
export class MySongsController {
15-
constructor(public readonly songService: SongService) {}
15+
constructor(
16+
@Inject(SongService)
17+
public readonly songService: SongService,
18+
) {}
1619

1720
@Get('/')
1821
@ApiOperation({

apps/backend/src/song/song.controller.spec.ts

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const mockSongService = {
3131
getSongEdit: jest.fn(),
3232
patchSong: jest.fn(),
3333
getSongDownloadUrl: jest.fn(),
34+
getSongFileBuffer: jest.fn(),
3435
deleteSong: jest.fn(),
3536
uploadSong: jest.fn(),
3637
getRandomSongs: jest.fn(),
@@ -46,6 +47,9 @@ describe('SongController', () => {
4647
let songService: SongService;
4748

4849
beforeEach(async () => {
50+
// Clear all mocks before each test
51+
jest.clearAllMocks();
52+
4953
const module: TestingModule = await Test.createTestingModule({
5054
controllers: [SongController],
5155
providers: [
@@ -66,8 +70,8 @@ describe('SongController', () => {
6670
songController = module.get<SongController>(SongController);
6771
songService = module.get<SongService>(SongService);
6872

69-
// Clear all mocks
70-
jest.clearAllMocks();
73+
// Verify the service is injected
74+
expect(songController.songService).toBeDefined();
7175
});
7276

7377
it('should be defined', () => {
@@ -79,7 +83,12 @@ describe('SongController', () => {
7983
const query: SongListQueryDTO = { page: 1, limit: 10 };
8084
const songList: SongPreviewDto[] = [];
8185

82-
mockSongService.getSongByPage.mockResolvedValueOnce(songList);
86+
mockSongService.querySongs.mockResolvedValueOnce({
87+
content: songList,
88+
page: 1,
89+
limit: 10,
90+
total: 0,
91+
});
8392

8493
const result = await songController.getSongList(query);
8594

@@ -88,7 +97,7 @@ describe('SongController', () => {
8897
expect(result.page).toBe(1);
8998
expect(result.limit).toBe(10);
9099
expect(result.total).toBe(0);
91-
expect(songService.getSongByPage).toHaveBeenCalled();
100+
expect(songService.querySongs).toHaveBeenCalled();
92101
});
93102

94103
it('should handle search query', async () => {
@@ -357,7 +366,7 @@ describe('SongController', () => {
357366
it('should handle errors', async () => {
358367
const query: SongListQueryDTO = { page: 1, limit: 10 };
359368

360-
mockSongService.getSongByPage.mockRejectedValueOnce(new Error('Error'));
369+
mockSongService.querySongs.mockRejectedValueOnce(new Error('Error'));
361370

362371
await expect(songController.getSongList(query)).rejects.toThrow('Error');
363372
});
@@ -437,7 +446,7 @@ describe('SongController', () => {
437446
expect(result.total).toBe(5);
438447
expect(result.page).toBe(1);
439448
expect(result.limit).toBe(10);
440-
expect(songService.querySongs).toHaveBeenCalledWith(query, q, undefined);
449+
expect(songService.querySongs).toHaveBeenCalledWith(query, q ?? '');
441450
});
442451

443452
it('should handle search with empty query string', async () => {
@@ -457,7 +466,7 @@ describe('SongController', () => {
457466
expect(result).toBeInstanceOf(PageDto);
458467
expect(result.content).toEqual(songList);
459468
expect(result.total).toBe(0);
460-
expect(songService.querySongs).toHaveBeenCalledWith(query, '', undefined);
469+
expect(songService.querySongs).toHaveBeenCalledWith(query, '');
461470
});
462471

463472
it('should handle search with null query string', async () => {
@@ -476,7 +485,7 @@ describe('SongController', () => {
476485

477486
expect(result).toBeInstanceOf(PageDto);
478487
expect(result.content).toEqual(songList);
479-
expect(songService.querySongs).toHaveBeenCalledWith(query, '', undefined);
488+
expect(songService.querySongs).toHaveBeenCalledWith(query, '');
480489
});
481490

482491
it('should handle search with multiple pages', async () => {
@@ -499,7 +508,7 @@ describe('SongController', () => {
499508
expect(result.content).toHaveLength(10);
500509
expect(result.total).toBe(25);
501510
expect(result.page).toBe(2);
502-
expect(songService.querySongs).toHaveBeenCalledWith(query, q, undefined);
511+
expect(songService.querySongs).toHaveBeenCalledWith(query, q ?? '');
503512
});
504513

505514
it('should handle search with large result set', async () => {
@@ -521,7 +530,7 @@ describe('SongController', () => {
521530
expect(result).toBeInstanceOf(PageDto);
522531
expect(result.content).toHaveLength(50);
523532
expect(result.total).toBe(500);
524-
expect(songService.querySongs).toHaveBeenCalledWith(query, q, undefined);
533+
expect(songService.querySongs).toHaveBeenCalledWith(query, q ?? '');
525534
});
526535

527536
it('should handle search on last page with partial results', async () => {
@@ -561,7 +570,7 @@ describe('SongController', () => {
561570
const result = await songController.searchSongs(query, q);
562571

563572
expect(result).toBeInstanceOf(PageDto);
564-
expect(songService.querySongs).toHaveBeenCalledWith(query, q, undefined);
573+
expect(songService.querySongs).toHaveBeenCalledWith(query, q ?? '');
565574
});
566575

567576
it('should handle search with very long query string', async () => {
@@ -579,7 +588,7 @@ describe('SongController', () => {
579588
const result = await songController.searchSongs(query, q);
580589

581590
expect(result).toBeInstanceOf(PageDto);
582-
expect(songService.querySongs).toHaveBeenCalledWith(query, q, undefined);
591+
expect(songService.querySongs).toHaveBeenCalledWith(query, q ?? '');
583592
});
584593

585594
it('should handle search with custom limit', async () => {
@@ -627,7 +636,7 @@ describe('SongController', () => {
627636

628637
expect(result).toBeInstanceOf(PageDto);
629638
expect(result.content).toHaveLength(10);
630-
expect(songService.querySongs).toHaveBeenCalledWith(query, q, undefined);
639+
expect(songService.querySongs).toHaveBeenCalledWith(query, q ?? '');
631640
});
632641

633642
it('should return correct pagination info with search results', async () => {
@@ -699,7 +708,7 @@ describe('SongController', () => {
699708
const result = await songController.searchSongs(query, q);
700709

701710
expect(result).toBeInstanceOf(PageDto);
702-
expect(songService.querySongs).toHaveBeenCalledWith(query, q, undefined);
711+
expect(songService.querySongs).toHaveBeenCalledWith(query, q ?? '');
703712
});
704713
});
705714

@@ -803,23 +812,31 @@ describe('SongController', () => {
803812

804813
const res = {
805814
set: jest.fn(),
806-
redirect: jest.fn(),
815+
send: jest.fn(),
807816
} as unknown as Response;
808817

809-
const url = 'test-url';
818+
const buffer = Buffer.from('test-song-data');
819+
const filename = 'test-song.nbs';
810820

811-
mockSongService.getSongDownloadUrl.mockResolvedValueOnce(url);
821+
mockSongService.getSongFileBuffer.mockResolvedValueOnce({
822+
buffer,
823+
filename,
824+
});
812825

813826
await songController.getSongFile(id, src, user, res);
814827

815828
expect(res.set).toHaveBeenCalledWith({
816-
'Content-Disposition': 'attachment; filename="song.nbs"',
829+
'Content-Type': 'application/octet-stream',
830+
'Content-Disposition': `attachment; filename="${filename.replace(
831+
/[/"]/g,
832+
'_',
833+
)}"`,
817834
'Access-Control-Expose-Headers': 'Content-Disposition',
818835
});
819836

820-
expect(res.redirect).toHaveBeenCalledWith(HttpStatus.FOUND, url);
837+
expect(res.send).toHaveBeenCalledWith(Buffer.from(buffer));
821838

822-
expect(songService.getSongDownloadUrl).toHaveBeenCalledWith(
839+
expect(songService.getSongFileBuffer).toHaveBeenCalledWith(
823840
id,
824841
user,
825842
src,
@@ -836,16 +853,16 @@ describe('SongController', () => {
836853

837854
const res = {
838855
set: jest.fn(),
839-
redirect: jest.fn(),
856+
send: jest.fn(),
840857
} as unknown as Response;
841858

842-
mockSongService.getSongDownloadUrl.mockRejectedValueOnce(
859+
mockSongService.getSongFileBuffer.mockRejectedValueOnce(
843860
new Error('Error'),
844861
);
845862

846863
await expect(
847864
songController.getSongFile(id, src, user, res),
848-
).rejects.toThrow('Error');
865+
).rejects.toThrow('An error occurred while retrieving the song file');
849866
});
850867
});
851868

apps/backend/src/song/song.controller.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
Headers,
99
HttpException,
1010
HttpStatus,
11+
Inject,
1112
Logger,
1213
Param,
1314
Patch,
@@ -65,7 +66,9 @@ export class SongController {
6566
};
6667

6768
constructor(
69+
@Inject(SongService)
6870
public readonly songService: SongService,
71+
@Inject(FileService)
6972
public readonly fileService: FileService,
7073
) {}
7174

apps/backend/src/song/song.service.spec.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { SongService } from './song.service';
2424
const mockFileService = {
2525
deleteSong: jest.fn(),
2626
getSongDownloadUrl: jest.fn(),
27+
getSongFile: jest.fn(),
2728
};
2829

2930
const mockSongUploadService = {
@@ -39,13 +40,26 @@ const mockSongWebhookService = {
3940
syncSongWebhook: jest.fn(),
4041
};
4142

43+
const mockSongModel = {
44+
create: jest.fn(),
45+
findOne: jest.fn(),
46+
find: jest.fn(),
47+
deleteOne: jest.fn(),
48+
countDocuments: jest.fn(),
49+
aggregate: jest.fn(),
50+
populate: jest.fn(),
51+
};
52+
4253
describe('SongService', () => {
4354
let service: SongService;
4455
let fileService: FileService;
4556
let songUploadService: SongUploadService;
4657
let songModel: Model<SongEntity>;
4758

4859
beforeEach(async () => {
60+
// Clear all mocks before each test
61+
jest.clearAllMocks();
62+
4963
const module: TestingModule = await Test.createTestingModule({
5064
providers: [
5165
SongService,
@@ -55,7 +69,7 @@ describe('SongService', () => {
5569
},
5670
{
5771
provide: getModelToken(SongEntity.name),
58-
useValue: mongoose.model(SongEntity.name, SongSchema),
72+
useValue: mockSongModel,
5973
},
6074
{
6175
provide: FileService,
@@ -1014,13 +1028,13 @@ describe('SongService', () => {
10141028
{ _id: 'category2', count: 5 },
10151029
];
10161030

1017-
jest.spyOn(songModel, 'aggregate').mockResolvedValue(categories);
1031+
mockSongModel.aggregate.mockResolvedValue(categories);
10181032

10191033
const result = await service.getCategories();
10201034

10211035
expect(result).toEqual({ category1: 10, category2: 5 });
10221036

1023-
expect(songModel.aggregate).toHaveBeenCalledWith([
1037+
expect(mockSongModel.aggregate).toHaveBeenCalledWith([
10241038
{ $match: { visibility: 'public' } },
10251039
{ $group: { _id: '$category', count: { $sum: 1 } } },
10261040
{ $sort: { count: -1 } },
@@ -1219,10 +1233,14 @@ describe('SongService', () => {
12191233
songList.map((song) => SongPreviewDto.fromSongDocumentWithUser(song)),
12201234
);
12211235

1222-
expect(songModel.aggregate).toHaveBeenCalledWith([
1236+
expect(mockSongModel.aggregate).toHaveBeenCalledWith([
12231237
{ $match: { visibility: 'public' } },
12241238
{ $sample: { size: count } },
12251239
]);
1240+
expect(mockSongModel.populate).toHaveBeenCalledWith(songList, {
1241+
path: 'uploader',
1242+
select: 'username profileImage -_id',
1243+
});
12261244
});
12271245

12281246
it('should return random songs with category filter', async () => {
@@ -1242,10 +1260,14 @@ describe('SongService', () => {
12421260
songList.map((song) => SongPreviewDto.fromSongDocumentWithUser(song)),
12431261
);
12441262

1245-
expect(songModel.aggregate).toHaveBeenCalledWith([
1263+
expect(mockSongModel.aggregate).toHaveBeenCalledWith([
12461264
{ $match: { visibility: 'public', category: 'pop' } },
12471265
{ $sample: { size: count } },
12481266
]);
1267+
expect(mockSongModel.populate).toHaveBeenCalledWith(songList, {
1268+
path: 'uploader',
1269+
select: 'username profileImage -_id',
1270+
});
12491271
});
12501272
});
12511273
});

0 commit comments

Comments
 (0)