feat: add a new command - /monitor delete (#23)
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #23
This commit is contained in:
Dawid Wysokiński 2022-10-23 07:44:20 +00:00
parent d983f5ae08
commit db2026c6e4
8 changed files with 257 additions and 7 deletions

View File

@ -193,6 +193,9 @@ func TestGroup_Get(t *testing.T) {
t.Run("OK", func(t *testing.T) {
t.Parallel()
g, err := repo.Get(context.Background(), group.ID.String(), group.ServerID)
assert.NoError(t, err)
assert.Equal(t, group.ToDomain(), g)
})
t.Run("ERR: group not found (unknown ID)", func(t *testing.T) {
@ -239,19 +242,30 @@ func TestGroup_Delete(t *testing.T) {
db := newDB(t)
fixture := loadFixtures(t, db)
repo := bundb.NewGroup(db)
groupRepo := bundb.NewGroup(db)
monitorRepo := bundb.NewMonitor(db)
group := getGroupFromFixture(t, fixture, "group-1-server-1")
t.Run("OK", func(t *testing.T) {
t.Parallel()
assert.NoError(t, repo.Delete(context.Background(), group.ID.String(), group.ServerID))
groups, err := repo.List(context.Background(), domain.ListGroupsParams{
ServerIDs: []string{group.ID.String()},
beforeDelete, err := groupRepo.List(context.Background(), domain.ListGroupsParams{
ServerIDs: []string{group.ServerID},
})
assert.NoError(t, err)
assert.Len(t, groups, 0)
assert.NoError(t, groupRepo.Delete(context.Background(), group.ID.String(), group.ServerID))
afterDelete, err := groupRepo.List(context.Background(), domain.ListGroupsParams{
ServerIDs: []string{group.ServerID},
})
assert.NoError(t, err)
assert.Len(t, afterDelete, len(beforeDelete)-1)
// monitors should also be deleted
monitors, err := monitorRepo.List(context.Background(), group.ID.String())
assert.NoError(t, err)
assert.Len(t, monitors, 0)
})
t.Run("ERR: group not found", func(t *testing.T) {
@ -287,7 +301,7 @@ func TestGroup_Delete(t *testing.T) {
assert.ErrorIs(
t,
repo.Delete(context.Background(), tt.id, tt.serverID),
groupRepo.Delete(context.Background(), tt.id, tt.serverID),
domain.GroupNotFoundError{ID: tt.id},
)
})

View File

@ -71,6 +71,47 @@ func (m *Monitor) List(ctx context.Context, groupID string) ([]domain.Monitor, e
return result, nil
}
func (m *Monitor) Get(ctx context.Context, id string) (domain.Monitor, error) {
if _, err := uuid.Parse(id); err != nil {
return domain.Monitor{}, domain.MonitorNotFoundError{ID: id}
}
var monitor model.Monitor
err := m.db.NewSelect().
Model(&monitor).
Where("id = ?", id).
Scan(ctx)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return domain.Monitor{}, domain.MonitorNotFoundError{ID: id}
}
return domain.Monitor{}, fmt.Errorf("couldn't select monitor from the db (id=%s): %w", id, err)
}
return monitor.ToDomain(), nil
}
func (m *Monitor) Delete(ctx context.Context, id string) error {
if _, err := uuid.Parse(id); err != nil {
return domain.MonitorNotFoundError{ID: id}
}
res, err := m.db.NewDelete().
Model(&model.Monitor{}).
Returning("NULL").
Where("id = ?", id).
Exec(ctx)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("couldn't delete monitor (id=%s): %w", id, err)
}
if affected, _ := res.RowsAffected(); affected == 0 {
return domain.MonitorNotFoundError{ID: id}
}
return nil
}
func mapCreateMonitorError(err error, params domain.CreateMonitorParams) error {
var pgError pgdriver.Error
if !errors.As(err, &pgError) {

View File

@ -138,3 +138,111 @@ func TestMonitor_List(t *testing.T) {
})
}
}
func TestMonitor_Get(t *testing.T) {
t.Parallel()
db := newDB(t)
fixture := loadFixtures(t, db)
repo := bundb.NewMonitor(db)
group := getGroupFromFixture(t, fixture, "group-1-server-1")
t.Run("OK", func(t *testing.T) {
t.Parallel()
monitors, err := repo.List(context.Background(), group.ID.String())
require.NoError(t, err)
require.Greater(t, len(monitors), 0)
m, err := repo.Get(context.Background(), monitors[0].ID)
assert.NoError(t, err)
assert.Equal(t, monitors[0], m)
})
t.Run("ERR: monitor not found", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
id string
}{
{
name: "ID - not UUID",
id: "test",
},
{
name: "ID - random UUID",
id: uuid.NewString(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
assert.ErrorIs(
t,
repo.Delete(context.Background(), tt.id),
domain.MonitorNotFoundError{ID: tt.id},
)
})
}
})
}
func TestMonitor_Delete(t *testing.T) {
t.Parallel()
db := newDB(t)
fixture := loadFixtures(t, db)
repo := bundb.NewMonitor(db)
group := getGroupFromFixture(t, fixture, "group-1-server-1")
t.Run("OK", func(t *testing.T) {
t.Parallel()
beforeDelete, err := repo.List(context.Background(), group.ID.String())
assert.NoError(t, err)
assert.Greater(t, len(beforeDelete), 0)
assert.NoError(t, repo.Delete(context.Background(), beforeDelete[0].ID))
afterDelete, err := repo.List(context.Background(), group.ID.String())
assert.NoError(t, err)
assert.Len(t, afterDelete, len(beforeDelete)-1)
})
t.Run("ERR: monitor not found", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
id string
}{
{
name: "ID - not UUID",
id: "test",
},
{
name: "ID - random UUID",
id: uuid.NewString(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
assert.ErrorIs(
t,
repo.Delete(context.Background(), tt.id),
domain.MonitorNotFoundError{ID: tt.id},
)
})
}
})
}

View File

@ -18,6 +18,7 @@ type GroupService interface {
type MonitorService interface {
Create(ctx context.Context, groupID, serverID, tribeTag string) (domain.Monitor, error)
Delete(ctx context.Context, id, serverID string) error
}
type ChoiceService interface {

View File

@ -57,6 +57,19 @@ func (c *monitorCommand) create(s *discordgo.Session) error {
},
},
},
{
Name: "delete",
Description: "Deletes a monitor",
Type: discordgo.ApplicationCommandOptionSubCommand,
Options: []*discordgo.ApplicationCommandOption{
{
Name: "monitor",
Description: "Monitor ID",
Type: discordgo.ApplicationCommandOptionString,
Required: true,
},
},
},
},
})
if err != nil {
@ -77,6 +90,9 @@ func (c *monitorCommand) handle(s *discordgo.Session, i *discordgo.InteractionCr
case "create":
c.handleCreate(s, i)
return
case "delete":
c.handleDelete(s, i)
return
default:
}
}
@ -116,3 +132,25 @@ func (c *monitorCommand) handleCreate(s *discordgo.Session, i *discordgo.Interac
},
})
}
func (c *monitorCommand) handleDelete(s *discordgo.Session, i *discordgo.InteractionCreate) {
ctx := context.Background()
monitor := i.ApplicationCommandData().Options[0].Options[0].StringValue()
if err := c.svc.Delete(ctx, monitor, i.GuildID); err != nil {
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: messageFromError(err),
},
})
return
}
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "monitor has been successfully deleted",
},
})
}

View File

@ -82,3 +82,19 @@ func (e MonitorLimitReachedError) UserError() string {
func (e MonitorLimitReachedError) Code() ErrorCode {
return ErrorCodeValidationError
}
type MonitorNotFoundError struct {
ID string
}
func (e MonitorNotFoundError) Error() string {
return fmt.Sprintf("monitor (ID=%s) not found", e.ID)
}
func (e MonitorNotFoundError) UserError() string {
return e.Error()
}
func (e MonitorNotFoundError) Code() ErrorCode {
return ErrorCodeEntityNotFound
}

View File

@ -91,3 +91,15 @@ func TestMonitorLimitReachedError(t *testing.T) {
assert.Equal(t, err.Error(), err.UserError())
assert.Equal(t, domain.ErrorCodeValidationError, err.Code())
}
func TestMonitorNotFoundError(t *testing.T) {
t.Parallel()
err := domain.MonitorNotFoundError{
ID: uuid.NewString(),
}
var _ domain.UserError = err
assert.Equal(t, fmt.Sprintf("monitor (ID=%s) not found", err.ID), err.Error())
assert.Equal(t, err.Error(), err.UserError())
assert.Equal(t, domain.ErrorCodeEntityNotFound, err.Code())
}

View File

@ -17,6 +17,8 @@ const (
type MonitorRepository interface {
Create(ctx context.Context, params domain.CreateMonitorParams) (domain.Monitor, error)
List(ctx context.Context, groupID string) ([]domain.Monitor, error)
Get(ctx context.Context, id string) (domain.Monitor, error)
Delete(ctx context.Context, id string) error
}
//counterfeiter:generate -o internal/mock/group_getter.gen.go . GroupGetter
@ -87,3 +89,21 @@ func (m *Monitor) Create(ctx context.Context, groupID, serverID, tribeTag string
return monitor, nil
}
func (m *Monitor) Delete(ctx context.Context, id, serverID string) error {
monitor, err := m.repo.Get(ctx, id)
if err != nil {
return fmt.Errorf("MonitorRepository.Get: %w", err)
}
_, err = m.groupSvc.Get(ctx, monitor.GroupID, serverID)
if err != nil {
return fmt.Errorf("GroupService.Get: %w", err)
}
if err = m.repo.Delete(ctx, id); err != nil {
return fmt.Errorf("MonitorRepository.Delete: %w", err)
}
return nil
}