Skip to content

Commit

Permalink
ensure unique offer URLs
Browse files Browse the repository at this point in the history
  • Loading branch information
kian99 committed Jul 16, 2024
1 parent 03107b0 commit bd3277f
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 5 deletions.
37 changes: 37 additions & 0 deletions internal/dbmodel/applicationoffer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
package dbmodel_test

import (
"database/sql"
"testing"

qt "github.com/frankban/quicktest"
"github.com/juju/juju/state"
"github.com/juju/names/v5"

"github.com/canonical/jimm/internal/dbmodel"
Expand All @@ -25,3 +27,38 @@ func TestApplicationOfferTag(t *testing.T) {
ao2.SetTag(tag.(names.ApplicationOfferTag))
c.Check(ao2, qt.DeepEquals, ao)
}

func TestApplicationOfferUniqueConstraint(t *testing.T) {
c := qt.New(t)
db := gormDB(c)
cl, cred, ctl, u := initModelEnv(c, db)

m := dbmodel.Model{
Name: "test-model",
UUID: sql.NullString{
String: "00000001-0000-0000-0000-0000-000000000001",
Valid: true,
},
Owner: u,
Controller: ctl,
CloudRegion: cl.Regions[0],
CloudCredential: cred,
Type: "iaas",
IsController: false,
DefaultSeries: "warty",
Life: state.Alive.String(),
}
c.Assert(db.Create(&m).Error, qt.IsNil)

ao := dbmodel.ApplicationOffer{
Name: "offer1",
UUID: "00000003-0000-0000-0000-0000-000000000001",
URL: "foo",
Model: m,
}
c.Assert(db.Create(&ao).Error, qt.IsNil)
ao.ID = 0
ao.Name = "offer2"
ao.UUID = "00000003-0000-0000-0000-0000-000000000002"
c.Assert(db.Create(&ao).Error, qt.ErrorMatches, `ERROR: duplicate key value violates unique constraint "application_offers_url_key" .*`)
}
4 changes: 4 additions & 0 deletions internal/dbmodel/sql/postgres/1_11.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- 1_11.sql is a migration that enforces uniqueness on URLs in application offers.
ALTER TABLE application_offers ADD UNIQUE (url);

UPDATE versions SET major=1, minor=11 WHERE component='jimmdb';
2 changes: 1 addition & 1 deletion internal/dbmodel/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const (
// Minor is the minor version of the model described in the dbmodel
// package. It should be incremented for any change made to the
// database model from database model in a released JIMM.
Minor = 10
Minor = 11
)

type Version struct {
Expand Down
18 changes: 14 additions & 4 deletions internal/jimm/applicationoffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package jimm
import (
"context"
"database/sql"
"fmt"
"sort"
"strings"

Expand Down Expand Up @@ -68,6 +69,19 @@ func (j *JIMM) Offer(ctx context.Context, user *openfga.User, offer AddApplicati
ApplicationName: offer.OfferName,
}

// Verify offer URL doesn't already exist.
var offerCheck dbmodel.ApplicationOffer
offerCheck.URL = offerURL.String()
err = j.Database.GetApplicationOffer(ctx, &offerCheck)
if err == nil {
return errors.E(fmt.Sprintf("offer %s already exists, use a different name", offerURL.String()), errors.CodeAlreadyExists)
} else {
if errors.ErrorCode(err) != errors.CodeNotFound {
// Anything besides Not Found is a problem.
return errors.E(op, err)
}
}

api, err := j.dial(ctx, &model.Controller, names.ModelTag{})
if err != nil {
return errors.E(op, err)
Expand Down Expand Up @@ -108,10 +122,6 @@ func (j *JIMM) Offer(ctx context.Context, user *openfga.User, offer AddApplicati

var doc dbmodel.ApplicationOffer
doc.FromJujuApplicationOfferAdminDetailsV5(offerDetails)
if err != nil {
zapctx.Error(ctx, "failed to convert application offer details", zaputil.Error(err))
return errors.E(op, err)
}
doc.ModelID = model.ID
err = j.Database.Transaction(func(db *db.Database) error {
if err := db.AddApplicationOffer(ctx, &doc); err != nil {
Expand Down
23 changes: 23 additions & 0 deletions internal/jujuapi/applicationoffers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,29 @@ func (s *applicationOffersSuite) TestOffer(c *gc.C) {
c.Assert(results[0].Error.Code, gc.Equals, "unauthorized access")
}

func (s *applicationOffersSuite) TestCreateMultipleOffersForSameApp(c *gc.C) {
conn := s.open(c, nil, "[email protected]")
defer conn.Close()
client := applicationoffers.NewClient(conn)

results, err := client.Offer(s.Model.UUID.String, "test-app", []string{s.endpoint.Name}, "[email protected]", "test-offer", "test offer description")
c.Assert(err, gc.Equals, nil)
c.Assert(results, gc.HasLen, 1)
c.Assert(results[0].Error, gc.Equals, (*jujuparams.Error)(nil))

// Creating an offer with the same name as above.
results, err = client.Offer(s.Model.UUID.String, "test-app", []string{s.endpoint.Name}, "[email protected]", "test-offer", "test offer description")
c.Assert(err, gc.Equals, nil)
c.Assert(results, gc.HasLen, 1)
c.Assert(results[0].Error, gc.ErrorMatches, `offer [email protected]/model-1.test-offer already exists, use a different name.*`)

// Creating an offer with a new name.
results, err = client.Offer(s.Model.UUID.String, "test-app", []string{s.endpoint.Name}, "[email protected]", "test-offer-foo", "test offer description")
c.Assert(err, gc.Equals, nil)
c.Assert(results, gc.HasLen, 1)
c.Assert(results[0].Error, gc.Equals, (*jujuparams.Error)(nil))
}

func (s *applicationOffersSuite) TestGetConsumeDetails(c *gc.C) {
conn := s.open(c, nil, "[email protected]")
defer conn.Close()
Expand Down

0 comments on commit bd3277f

Please sign in to comment.