-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13 from Artur-Poffo/feat-register-tag
Feat(Tag): Register many tags at same time
- Loading branch information
Showing
8 changed files
with
174 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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 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
7 changes: 7 additions & 0 deletions
7
src/domain/course-management/application/repositories/tags-repository.ts
This file contains 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,7 @@ | ||
import { type Tag } from '../../enterprise/entities/tag' | ||
|
||
export interface TagsRepository { | ||
findById: (id: string) => Promise<Tag | null> | ||
findByValue: (value: string) => Promise<Tag | null> | ||
create: (tag: Tag) => Promise<Tag> | ||
} |
7 changes: 7 additions & 0 deletions
7
src/domain/course-management/application/use-cases/errors/repeated-tag-error.ts
This file contains 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,7 @@ | ||
import { type UseCaseError } from '@/core/errors/use-case-error' | ||
|
||
export class RepeatedTagError extends Error implements UseCaseError { | ||
constructor() { | ||
super('Repeated tag found') | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
src/domain/course-management/application/use-cases/errors/tag-already-exists-error.ts
This file contains 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,7 @@ | ||
import { type UseCaseError } from '@/core/errors/use-case-error' | ||
|
||
export class TagAlreadyExistsError extends Error implements UseCaseError { | ||
constructor(name: string) { | ||
super(`Tag "${name}" already exists.`) | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
src/domain/course-management/application/use-cases/register-tag.spec.ts
This file contains 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,63 @@ | ||
import { InMemoryTagsRepository } from './../../../../../test/repositories/in-memory-tags-repository' | ||
import { RepeatedTagError } from './errors/repeated-tag-error' | ||
import { TagAlreadyExistsError } from './errors/tag-already-exists-error' | ||
import { RegisterTagUseCase } from './register-tag' | ||
|
||
let inMemoryTagsRepository: InMemoryTagsRepository | ||
let sut: RegisterTagUseCase | ||
|
||
describe('Register tag use case', () => { | ||
beforeEach(() => { | ||
inMemoryTagsRepository = new InMemoryTagsRepository() | ||
sut = new RegisterTagUseCase(inMemoryTagsRepository) | ||
}) | ||
|
||
it('should be able to register a new tag for the application', async () => { | ||
const result = await sut.exec({ | ||
tags: 'TypeScript' | ||
}) | ||
|
||
expect(result.isRight()).toBe(true) | ||
expect(inMemoryTagsRepository.items[0].value).toBe('TYPESCRIPT') | ||
}) | ||
|
||
it('should be able to register many tags for the application at the same time', async () => { | ||
const result = await sut.exec({ | ||
tags: ['TypeScript', 'Next.js', 'Vue.js'] | ||
}) | ||
|
||
expect(result.isRight()).toBe(true) | ||
expect(result.value).toMatchObject({ | ||
tags: expect.arrayContaining([ | ||
expect.objectContaining({ | ||
value: 'TYPESCRIPT' | ||
}), | ||
expect.objectContaining({ | ||
value: 'VUE.JS' | ||
}) | ||
]) | ||
}) | ||
}) | ||
|
||
it('should not be able to register a new tag for the application with same value twice', async () => { | ||
await sut.exec({ | ||
tags: 'TypeScript' | ||
}) | ||
|
||
const result = await sut.exec({ | ||
tags: 'TypeScript' | ||
}) | ||
|
||
expect(result.isLeft()).toBe(true) | ||
expect(result.value).toBeInstanceOf(TagAlreadyExistsError) | ||
}) | ||
|
||
it('should not be able to register a new tag for the application with same value twice in same exec', async () => { | ||
const result = await sut.exec({ | ||
tags: ['TypeScript', 'TypeScript'] | ||
}) | ||
|
||
expect(result.isLeft()).toBe(true) | ||
expect(result.value).toBeInstanceOf(RepeatedTagError) | ||
}) | ||
}) |
56 changes: 56 additions & 0 deletions
56
src/domain/course-management/application/use-cases/register-tag.ts
This file contains 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,56 @@ | ||
import { left, right, type Either } from '@/core/either' | ||
import { type UseCase } from '@/core/use-cases/use-case' | ||
import { Tag } from '../../enterprise/entities/tag' | ||
import { type TagsRepository } from '../repositories/tags-repository' | ||
import { RepeatedTagError } from './errors/repeated-tag-error' | ||
import { TagAlreadyExistsError } from './errors/tag-already-exists-error' | ||
|
||
interface RegisterTagUseCaseRequest { | ||
tags: string | string[] | ||
} | ||
|
||
type RegisterTagUseCaseResponse = Either< | ||
TagAlreadyExistsError, | ||
{ | ||
tags: Tag[] | ||
} | ||
> | ||
|
||
export class RegisterTagUseCase implements UseCase<RegisterTagUseCaseRequest, RegisterTagUseCaseResponse> { | ||
constructor( | ||
private readonly tagsRepository: TagsRepository | ||
) { } | ||
|
||
async exec({ | ||
tags | ||
}: RegisterTagUseCaseRequest): Promise<RegisterTagUseCaseResponse> { | ||
const tagsToRegister = Array.isArray(tags) ? tags : [tags] | ||
|
||
const haveRepeatedTags = new Set(tagsToRegister).size !== tagsToRegister.length | ||
|
||
if (haveRepeatedTags) { | ||
return left(new RepeatedTagError()) | ||
} | ||
|
||
const existingTags = await Promise.all( | ||
tagsToRegister.map(async (tagValue) => ({ | ||
tagValue: tagValue.toUpperCase(), | ||
exists: !!(await this.tagsRepository.findByValue(tagValue.toUpperCase())) | ||
})) | ||
) | ||
|
||
const alreadyExists = existingTags.find((tag) => tag.exists) | ||
|
||
if (alreadyExists) { | ||
return left(new TagAlreadyExistsError(alreadyExists.tagValue)) | ||
} | ||
|
||
const newTags = tagsToRegister.map((value) => Tag.create({ value: value.toUpperCase() })) | ||
|
||
await Promise.all(newTags.map(async (tag) => await this.tagsRepository.create(tag))) | ||
|
||
return right({ | ||
tags: newTags | ||
}) | ||
} | ||
} |
This file contains 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,31 @@ | ||
import { type TagsRepository } from '@/domain/course-management/application/repositories/tags-repository' | ||
import { type Tag } from '@/domain/course-management/enterprise/entities/tag' | ||
|
||
export class InMemoryTagsRepository implements TagsRepository { | ||
public items: Tag[] = [] | ||
|
||
async findById(id: string): Promise<Tag | null> { | ||
const tag = this.items.find(tagToCompare => tagToCompare.id.toString() === id) | ||
|
||
if (!tag) { | ||
return null | ||
} | ||
|
||
return tag | ||
} | ||
|
||
async findByValue(value: string): Promise<Tag | null> { | ||
const tag = this.items.find(tagToCompare => tagToCompare.value === value) | ||
|
||
if (!tag) { | ||
return null | ||
} | ||
|
||
return tag | ||
} | ||
|
||
async create(tag: Tag): Promise<Tag> { | ||
this.items.push(tag) | ||
return tag | ||
} | ||
} |