Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions internal/organisations/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,22 +218,3 @@ func (s Service) Update(ctx context.Context, params UpdateOrgParams) (Organisati

return toOrganisationDTO(model), nil
}

// AssociateProductToOrg associates a product with an organisation based on the provided organisation and product IDs.
func (s Service) AssociateProductToOrg(ctx context.Context, orgID int64, productID int64) error {
logger := logging.FromContext(ctx).With("service", "organisations")

logger.Debug("Associating product to organisation", "orgID", orgID, "productID", productID)

err := s.store.AddProductToOrganisation(ctx, database.AddProductToOrganisationParams{
OrganisationID: sql.NullInt64{Int64: orgID, Valid: true},
ProductID: sql.NullInt64{Int64: productID, Valid: true},
})

if err != nil {
logger.Error("Error associating product to organisation", "error", err)
return err
}

return nil
}
27 changes: 22 additions & 5 deletions internal/organisations/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"testing"
"watchtower/internal/database"

"github.com/code-gorilla-au/odize"
)
Expand Down Expand Up @@ -116,16 +117,32 @@ func TestService(t *testing.T) {
odize.AssertNoError(t, err)

}).
Test("AssociateProductToOrg should create link in product_organisations", func(t *testing.T) {
org, _ := s.Create(ctx, CreateOrgParams{FriendlyName: "Product Org", Namespace: "prod-ns"})
productID := int64(123)
Test("GetOrgAssociatedToProduct should return the organisation associated with a product", func(t *testing.T) {

err := s.AssociateProductToOrg(ctx, org.ID, productID)
org, err := s.Create(ctx, CreateOrgParams{
FriendlyName: "Associated Org",
Namespace: "assoc-ns",
})
odize.AssertNoError(t, err)

fetchedOrg, err := s.GetOrgAssociatedToProduct(ctx, productID)
prod, err := _testDB.CreateProduct(ctx, database.CreateProductParams{
Name: "Test Product",
Description: "Test Description",
})
odize.AssertNoError(t, err)

err = _testDB.AddProductToOrganisation(ctx, database.AddProductToOrganisationParams{
ProductID: sql.NullInt64{Int64: prod.ID, Valid: true},
OrganisationID: sql.NullInt64{Int64: org.ID, Valid: true},
})
odize.AssertNoError(t, err)

fetchedOrg, err := s.GetOrgAssociatedToProduct(ctx, prod.ID)
odize.AssertNoError(t, err)

odize.AssertEqual(t, org.ID, fetchedOrg.ID)
odize.AssertEqual(t, org.FriendlyName, fetchedOrg.FriendlyName)
odize.AssertEqual(t, org.Namespace, fetchedOrg.Namespace)
}).
Run()

Expand Down
1 change: 1 addition & 0 deletions internal/products/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type ProductBaseStore interface {
UpdateProduct(ctx context.Context, arg database.UpdateProductParams) (database.Product, error)
UpdateProductSync(ctx context.Context, id int64) error
DeleteProduct(ctx context.Context, id int64) error
AddProductToOrganisation(ctx context.Context, arg database.AddProductToOrganisationParams) error
}

type RepoStore interface {
Expand Down
10 changes: 10 additions & 0 deletions internal/products/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ func (s *Service) Create(ctx context.Context, params CreateProductParams) (Produ
return ProductDTO{}, err
}

err = s.store.AddProductToOrganisation(ctx, database.AddProductToOrganisationParams{
OrganisationID: sql.NullInt64{Int64: params.OrganisationID, Valid: true},
ProductID: sql.NullInt64{Int64: prod.ID, Valid: true},
})
if err != nil {
logger.Error("Error associating product to organisation", "error", err)

return ProductDTO{}, err
}

return toProductDTO(prod), nil
}

Expand Down
56 changes: 29 additions & 27 deletions internal/products/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@ func TestService(t *testing.T) {
})

err := group.
Test("Create should create a product", func(t *testing.T) {
Test("Create should create a product and associate it with an organisation", func(t *testing.T) {
org, _ := _testDB.CreateOrganisation(ctx, database.CreateOrganisationParams{
FriendlyName: "Test Org Create",
Namespace: "test-ns-create",
})
params := CreateProductParams{
Name: "Test Product",
Desc: "Test Description",
Tags: []string{"tag1", "tag2"},
Name: "Test Product",
Desc: "Test Description",
Tags: []string{"tag1", "tag2"},
OrganisationID: org.ID,
}

prod, err := s.Create(ctx, params)
Expand All @@ -38,6 +43,11 @@ func TestService(t *testing.T) {
odize.AssertEqual(t, params.Desc, prod.Description)
odize.AssertEqual(t, 2, len(prod.Tags))
odize.AssertTrue(t, prod.ID > 0)

// Verify association
fetchedOrg, err := _testDB.GetOrganisationForProduct(ctx, sql.NullInt64{Int64: prod.ID, Valid: true})
odize.AssertNoError(t, err)
odize.AssertEqual(t, org.ID, fetchedOrg.ID)
}).
Test("Get should return a product", func(t *testing.T) {
params := CreateProductParams{
Expand Down Expand Up @@ -248,11 +258,12 @@ func TestService(t *testing.T) {
}).
Test("GetPullRequests and GetPullRequestByOrg", func(t *testing.T) {
tag := fmt.Sprintf("pr-tag-%d", time.Now().UnixNano())
prod, _ := s.Create(ctx, CreateProductParams{Name: "PR Product", Tags: []string{tag}})
orgID := int64(456)
_ = _testDB.AddProductToOrganisation(ctx, database.AddProductToOrganisationParams{
ProductID: sql.NullInt64{Int64: prod.ID, Valid: true},
OrganisationID: sql.NullInt64{Int64: orgID, Valid: true},

prod, _ := s.Create(ctx, CreateProductParams{
Name: "PR Product",
Tags: []string{tag},
OrganisationID: orgID,
})

repoName := "pr-repo"
Expand Down Expand Up @@ -281,6 +292,7 @@ func TestService(t *testing.T) {
odize.AssertTrue(t, len(orgPrs) > 0)
}).
Test("GetRecentPullRequests should return external IDs of recent PRs", func(t *testing.T) {

params := CreatePRParams{
ExternalID: uuid.New().String(),
Title: uuid.New().String(),
Expand All @@ -294,24 +306,14 @@ func TestService(t *testing.T) {
err := s.CreateRepo(ctx, CreateRepoParams{Name: params.RepositoryName, Topic: "tag", Owner: "owner"})
odize.AssertNoError(t, err)

prodID, err := s.Create(ctx, CreateProductParams{
Name: params.RepositoryName,
Desc: "",
Tags: []string{"tag"},
_, err = s.Create(ctx, CreateProductParams{
Name: params.RepositoryName,
Desc: "",
Tags: []string{"tag"},
OrganisationID: 1,
})
odize.AssertNoError(t, err)

_ = _testDB.AddProductToOrganisation(ctx, database.AddProductToOrganisationParams{
ProductID: sql.NullInt64{
Int64: prodID.ID,
Valid: true,
},
OrganisationID: sql.NullInt64{
Int64: 0,
Valid: true,
},
})

err = s.CreatePullRequest(ctx, params)
odize.AssertNoError(t, err)

Expand All @@ -330,11 +332,11 @@ func TestService(t *testing.T) {
}).
Test("GetSecurity and GetSecurityByOrg", func(t *testing.T) {
tag := fmt.Sprintf("sec-tag-%d", time.Now().UnixNano())
prod, _ := s.Create(ctx, CreateProductParams{Name: "Sec Product", Tags: []string{tag}})
orgID := int64(789)
_ = _testDB.AddProductToOrganisation(ctx, database.AddProductToOrganisationParams{
ProductID: sql.NullInt64{Int64: prod.ID, Valid: true},
OrganisationID: sql.NullInt64{Int64: orgID, Valid: true},
prod, _ := s.Create(ctx, CreateProductParams{
Name: "Sec Product",
Tags: []string{tag},
OrganisationID: orgID,
})

repoName := "sec-repo"
Expand Down
7 changes: 4 additions & 3 deletions internal/products/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ type SecurityDTO struct {
}

type CreateProductParams struct {
Name string
Desc string
Tags []string
Name string
Desc string
Tags []string
OrganisationID int64
}

type UpdateProductParams struct {
Expand Down
81 changes: 81 additions & 0 deletions internal/watchtower/notifications_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package watchtower

import (
"context"
"strings"
"testing"
"time"
"watchtower/internal/notifications"
"watchtower/internal/products"

"github.com/code-gorilla-au/odize"
"github.com/google/uuid"
Expand Down Expand Up @@ -82,6 +84,85 @@ func TestService_Notifications(t *testing.T) {

odize.AssertEqual(t, 0, len(unread))
}).
Test("CreateUnreadPRNotification should create notifications for recent PRs", func(t *testing.T) {
// Setup: Org, Product, Repo, PR
org, err := s.CreateOrganisation("Test Org PR", "test-org-pr", "token", "desc")
odize.AssertNoError(t, err)

_, err = s.CreateProduct("Test Product PR", "desc", []string{"tag-pr"}, org.ID)
odize.AssertNoError(t, err)

err = s.productSvc.CreateRepo(ctx, products.CreateRepoParams{
Name: "repo-pr",
Url: "url",
Topic: "tag-pr",
Owner: "owner",
})
odize.AssertNoError(t, err)

externalID := uuid.New().String()
err = s.productSvc.CreatePullRequest(ctx, products.CreatePRParams{
ExternalID: externalID,
Title: "PR Title",
RepositoryName: "repo-pr",
Url: "url",
State: "OPEN",
Author: "author",
CreatedAt: time.Now(),
})
odize.AssertNoError(t, err)

// Action
err = s.CreateUnreadPRNotification()
odize.AssertNoError(t, err)

// Verify
unreadNotification, err := s.notificationSvc.GetNotificationByExternalID(ctx, externalID)
odize.AssertNoError(t, err)

odize.AssertEqual(t, "OPEN_PULL_REQUEST", unreadNotification.Type)
odize.AssertTrue(t, strings.Contains(unreadNotification.Content, "repo-pr"))
odize.AssertTrue(t, strings.Contains(unreadNotification.Content, "New pull request"))
}).
Test("CreateUnreadSecurityNotification should create notifications for recent security alerts", func(t *testing.T) {
// Setup: Org, Product, Repo, Security Alert
org, err := s.CreateOrganisation("Test Org Sec", "test-org-sec", "token", "desc")
odize.AssertNoError(t, err)

_, err = s.CreateProduct("Test Product Sec", "desc", []string{"tag-sec"}, org.ID)
odize.AssertNoError(t, err)

err = s.productSvc.CreateRepo(ctx, products.CreateRepoParams{
Name: "repo-sec",
Url: "url",
Topic: "tag-sec",
Owner: "owner",
})
odize.AssertNoError(t, err)

externalID := uuid.New().String()
err = s.productSvc.UpsertSecurity(ctx, products.CreateSecurityParams{
ExternalID: externalID,
RepositoryName: "repo-sec",
PackageName: "pkg",
State: "OPEN",
Severity: "HIGH",
CreatedAt: time.Now(),
})
odize.AssertNoError(t, err)

// Action
err = s.CreateUnreadSecurityNotification()
odize.AssertNoError(t, err)

// Verify
unreadNotification, err := s.notificationSvc.GetNotificationByExternalID(ctx, externalID)
odize.AssertNoError(t, err)

odize.AssertEqual(t, "OPEN_SECURITY_ALERT", unreadNotification.Type)
odize.AssertTrue(t, strings.Contains(unreadNotification.Content, "repo-sec"))
odize.AssertTrue(t, strings.Contains(unreadNotification.Content, "New security alert"))
}).
Run()

odize.AssertNoError(t, err)
Expand Down
68 changes: 68 additions & 0 deletions internal/watchtower/organisations_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package watchtower

import (
"context"
"testing"

"github.com/code-gorilla-au/odize"
)

func TestService_Organisations(t *testing.T) {
group := odize.NewGroup(t, nil)

var s *Service
ctx := context.Background()

group.BeforeEach(func() {
s = NewService(ctx, _testDB, _testTxnDB)
})

err := group.
Test("DeleteAllOrgs should delete all organisations and their products", func(t *testing.T) {
// 1. Create orgs
org1, err := s.CreateOrganisation("Org 1", "ns-1", "token-1", "desc-1")
odize.AssertNoError(t, err)

org2, err := s.CreateOrganisation("Org 2", "ns-2", "token-2", "desc-2")
odize.AssertNoError(t, err)

// 2. Create products for orgs
_, err = s.CreateProduct("Prod 1", "desc-p1", []string{"tag1"}, org1.ID)
odize.AssertNoError(t, err)

_, err = s.CreateProduct("Prod 2", "desc-p2", []string{"tag2"}, org2.ID)
odize.AssertNoError(t, err)

// 3. Verify they exist
allOrgs, err := s.GetAllOrganisations()
odize.AssertNoError(t, err)
odize.AssertTrue(t, len(allOrgs) >= 2)

prods1, err := s.GetAllProductsForOrganisation(org1.ID)
odize.AssertNoError(t, err)
odize.AssertEqual(t, 1, len(prods1))

prods2, err := s.GetAllProductsForOrganisation(org2.ID)
odize.AssertNoError(t, err)
odize.AssertEqual(t, 1, len(prods2))

// 4. Delete all
err = s.DeleteAllOrgs()
odize.AssertNoError(t, err)

// 5. Verify they are gone
remainingOrgs, err := s.GetAllOrganisations()
odize.AssertNoError(t, err)
odize.AssertEqual(t, 0, len(remainingOrgs))

// Check if products are also gone
remainingProds1, _ := s.productSvc.GetByOrg(ctx, org1.ID)
odize.AssertEqual(t, 0, len(remainingProds1))

remainingProds2, _ := s.productSvc.GetByOrg(ctx, org2.ID)
odize.AssertEqual(t, 0, len(remainingProds2))
}).
Run()

odize.AssertNoError(t, err)
}
Loading