diff --git a/core/deleter/service.go b/core/deleter/service.go index a2ccf2965..a3b780249 100644 --- a/core/deleter/service.go +++ b/core/deleter/service.go @@ -270,9 +270,15 @@ func (d Service) deleteProject(ctx context.Context, prj project.Project) error { } } - if err := d.projService.DeleteModel(ctx, prj.ID); err != nil && !errors.Is(err, project.ErrNotExist) { + if err := d.projService.DeleteModel(ctx, prj.ID); err != nil { + if errors.Is(err, project.ErrNotExist) { + return nil + } return err } + if err := audit.NewLogger(ctx, prj.Organization.ID).Log(audit.ProjectDeletedEvent, audit.ProjectTarget(prj.ID)); err != nil { + slog.WarnContext(ctx, "failed to write audit log", "error", err, "event", audit.ProjectDeletedEvent) + } return nil } diff --git a/core/deleter/service_test.go b/core/deleter/service_test.go index ad3f74aac..f928378d1 100644 --- a/core/deleter/service_test.go +++ b/core/deleter/service_test.go @@ -164,9 +164,31 @@ func TestDeleteProject(t *testing.T) { m.resSvc.EXPECT().List(mock.Anything, resource.Filter{ProjectID: projectID}). Return([]resource.Resource{}, nil) m.projSvc.EXPECT().DeleteModel(mock.Anything, projectID).Return(project.ErrNotExist) + repo := &fakeAuditRepository{} - err := m.build().DeleteProject(context.Background(), projectID) + err := m.build().DeleteProject(auditContext(repo), projectID) assert.NoError(t, err) + assert.Empty(t, repo.logs) + }) + + t.Run("publishes the project deleted event with the project's org", func(t *testing.T) { + m := newMocks(t) + m.projSvc.EXPECT().List(mock.Anything, project.Filter{ProjectIDs: []string{projectID}, IncludeDisabled: true}). + Return([]project.Project{{ID: projectID, Name: "p1", Organization: organization.Organization{ID: "org-1"}}}, nil) + m.polSvc.EXPECT().List(mock.Anything, policy.Filter{ProjectID: projectID}). + Return([]policy.Policy{}, nil) + m.resSvc.EXPECT().List(mock.Anything, resource.Filter{ProjectID: projectID}). + Return([]resource.Resource{}, nil) + m.projSvc.EXPECT().DeleteModel(mock.Anything, projectID).Return(nil) + repo := &fakeAuditRepository{} + + err := m.build().DeleteProject(auditContext(repo), projectID) + assert.NoError(t, err) + if assert.Len(t, repo.logs, 1) { + assert.Equal(t, audit.ProjectDeletedEvent.String(), repo.logs[0].Action) + assert.Equal(t, "org-1", repo.logs[0].OrgID) + assert.Equal(t, audit.ProjectTarget(projectID), repo.logs[0].Target) + } }) t.Run("a resource that is already gone does not stop the delete", func(t *testing.T) { diff --git a/test/e2e/regression/api_test.go b/test/e2e/regression/api_test.go index a85a1ad53..d88d6c9d6 100644 --- a/test/e2e/regression/api_test.go +++ b/test/e2e/regression/api_test.go @@ -3907,6 +3907,16 @@ func (s *APIRegressionTestSuite) TestOrganizationDeleteAlsoDeletesDisabledProjec s.Assert().Equal(1, deletedResources) }) + s.Run("each project of the org is published as an app.project.deleted event", func() { + for _, id := range []string{enabledProjectID, disabledProjectID} { + var events int + s.Require().NoError(s.dbc.GetContext(context.Background(), &events, + "SELECT count(*) FROM auditlogs WHERE action = $1 AND org_id = $2 AND target->>'ID' = $3", + "app.project.deleted", orgID, id)) + s.Assert().Equal(1, events, id) + } + }) + s.Run("the org is gone, a second delete is refused", func() { _, err := s.testBench.Client.DeleteOrganization(ctxOrgAdminAuth, connect.NewRequest(&frontierv1beta1.DeleteOrganizationRequest{Id: orgID})) s.Require().Error(err) @@ -4071,6 +4081,14 @@ func (s *APIRegressionTestSuite) TestProjectDeleteKeepsTheRowAndRemovesAccess() s.Assert().Equal(1, deletedRows) }) + s.Run("the delete is published as an app.project.deleted event", func() { + var events int + s.Require().NoError(s.dbc.GetContext(context.Background(), &events, + "SELECT count(*) FROM auditlogs WHERE action = $1 AND org_id = $2 AND target->>'ID' = $3", + "app.project.deleted", orgID, projectID)) + s.Assert().Equal(1, events) + }) + s.Run("a second delete is refused", func() { _, err := s.testBench.Client.DeleteProject(ctxOrgAdminAuth, connect.NewRequest(&frontierv1beta1.DeleteProjectRequest{Id: projectID})) s.Require().Error(err)