Skip to content

Commit

Permalink
feat: add vitality field to Software entities (#235)
Browse files Browse the repository at this point in the history
  • Loading branch information
bfabio committed Mar 24, 2024
1 parent 6f57a24 commit 42f0820
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 7 deletions.
15 changes: 15 additions & 0 deletions developers-italia.oas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1321,6 +1321,21 @@ components:
software (fe. when the repository is empty or there's something malicious going on,
but the problem is expected to be fixed in the near future) without removing it.
default: true
vitality:
type: string
description: >
The vitality of the software repository, indicating
health, activity, or metrics relevant to its condition.
This is a opaque string, and users of the API can choose their
own format.
The Italian catalog, for example, uses comma-separated values for
daily vitality scores (0-100) (see https://github.com/italia/publiccode-crawler/blob/main/vitality-ranges.yml).
minLength: 1
maxLength: 99999
pattern: '.*'
default: null
example: "90,100,94,12"
createdAt:
type: string
format: date-time
Expand Down
2 changes: 2 additions & 0 deletions internal/common/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ type SoftwarePost struct {
Aliases []string `json:"aliases" validate:"dive,url"`
PubliccodeYml string `json:"publiccodeYml" validate:"required"`
Active *bool `json:"active"`
Vitality *string `json:"vitality"`
}

type SoftwarePatch struct {
URL *string `json:"url" validate:"omitempty,url"`
Aliases *[]string `json:"aliases" validate:"omitempty,dive,url"`
PubliccodeYml *string `json:"publiccodeYml"`
Active *bool `json:"active"`
Vitality *string `json:"vitality"`
}

type Log struct {
Expand Down
1 change: 1 addition & 0 deletions internal/handlers/software.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ func (p *Software) PostSoftware(ctx *fiber.Ctx) error {
Aliases: aliases,
PubliccodeYml: softwareReq.PubliccodeYml,
Active: softwareReq.Active,
Vitality: softwareReq.Vitality,
}

if err := p.db.Create(&software).Error; err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type Software struct {
PubliccodeYml string `json:"publiccodeYml"`
Logs []Log `json:"-" gorm:"polymorphic:Entity;"`
Active *bool `json:"active" gorm:"default:true;not null"`
Vitality *string `json:"vitality"`
CreatedAt time.Time `json:"createdAt" gorm:"index"`
UpdatedAt time.Time `json:"updatedAt"`
}
Expand Down
151 changes: 144 additions & 7 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1358,7 +1358,7 @@ func TestSoftwareEndpoints(t *testing.T) {
assert.Equal(t, true, firstSoftware["active"])

for key := range firstSoftware {
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active"}, key)
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active", "vitality"}, key)
}
},
},
Expand Down Expand Up @@ -1401,7 +1401,7 @@ func TestSoftwareEndpoints(t *testing.T) {
assert.Nil(t, err)

for key := range firstSoftware {
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active"}, key)
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active", "vitality"}, key)
}
},
},
Expand Down Expand Up @@ -1442,7 +1442,7 @@ func TestSoftwareEndpoints(t *testing.T) {
assert.Nil(t, err)

for key := range firstSoftware {
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active"}, key)
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active", "vitality"}, key)
}
},
},
Expand Down Expand Up @@ -1479,7 +1479,7 @@ func TestSoftwareEndpoints(t *testing.T) {
assert.Equal(t, "2014-05-01T00:00:00Z", firstSoftware["updatedAt"])

for key := range firstSoftware {
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active"}, key)
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active", "vitality"}, key)
}
},
},
Expand Down Expand Up @@ -1670,7 +1670,33 @@ func TestSoftwareEndpoints(t *testing.T) {
assert.Nil(t, err)

for key := range response {
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active"}, key)
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active", "vitality"}, key)
}
},
}, {
description: "GET software with vitality field",
query: "GET /v1/software/9f135268-a37e-4ead-96ec-e4a24bb9344a",
expectedCode: 200,
expectedContentType: "application/json",
validateFunc: func(t *testing.T, response map[string]interface{}) {
assert.NotEmpty(t, response["publiccodeYml"])

assert.Equal(t, "https://2-a.example.org/code/repo", response["url"])

assert.IsType(t, []interface{}{}, response["aliases"])
assert.Equal(t, 1, len(response["aliases"].([]interface{})))

match, err := regexp.MatchString(UUID_REGEXP, response["id"].(string))
assert.Nil(t, err)
assert.True(t, match)

_, err = time.Parse(time.RFC3339, response["createdAt"].(string))
assert.Nil(t, err)
_, err = time.Parse(time.RFC3339, response["updatedAt"].(string))
assert.Nil(t, err)

for key := range response {
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active", "vitality"}, key)
}
},
},
Expand Down Expand Up @@ -1705,7 +1731,7 @@ func TestSoftwareEndpoints(t *testing.T) {
assert.Equal(t, true, response["active"])

for key := range response {
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active"}, key)
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active", "vitality"}, key)
}

// TODO: check the record was actually created in the database
Expand Down Expand Up @@ -1745,14 +1771,51 @@ func TestSoftwareEndpoints(t *testing.T) {
assert.Nil(t, err)

for key := range response {
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active"}, key)
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active", "vitality"}, key)
}

// TODO: check the record was actually created in the database
// TODO: check there are no dangling software_urls
},
},
{
description: "POST software with vitality field",
query: "POST /v1/software",
body: `{"publiccodeYml": "-", "url": "https://software.example.net", "vitality": "90,90,90"}`,
headers: map[string][]string{
"Authorization": {goodToken},
"Content-Type": {"application/json"},
},
expectedCode: 200,
expectedContentType: "application/json",
expectedBody: "x",
validateFunc: func(t *testing.T, response map[string]interface{}) {
assert.Equal(t, "https://software.example.net", response["url"])
assert.NotEmpty(t, response["publiccodeYml"])

assert.IsType(t, []interface{}{}, response["aliases"])
assert.Empty(t, response["aliases"].([]interface{}))

assert.Equal(t, "90,90,90", response["vitality"])

match, err := regexp.MatchString(UUID_REGEXP, response["id"].(string))
assert.Nil(t, err)
assert.True(t, match)

_, err = time.Parse(time.RFC3339, response["createdAt"].(string))
assert.Nil(t, err)

_, err = time.Parse(time.RFC3339, response["updatedAt"].(string))
assert.Nil(t, err)

for key := range response {
assert.Contains(t, []string{"id", "createdAt", "updatedAt", "url", "aliases", "publiccodeYml", "active", "vitality"}, key)
}

// TODO: check the record was actually created in the database
// TODO: check there are no dangling software_urls
},
},
{
description: "POST software with invalid payload",
query: "POST /v1/software",
Expand Down Expand Up @@ -2078,6 +2141,44 @@ func TestSoftwareEndpoints(t *testing.T) {
assert.Greater(t, updated, created)
},
},
{
description: "PATCH software, vitality",
query: "PATCH /v1/software/59803fb7-8eec-4fe5-a354-8926009c364a",
body: `{"vitality": "80,90,99"}`,
headers: map[string][]string{
"Authorization": {goodToken},
"Content-Type": {"application/json"},
},

expectedCode: 200,
expectedContentType: "application/json",
validateFunc: func(t *testing.T, response map[string]interface{}) {
assert.Equal(t, true, response["active"])
assert.Equal(t, "https://18-a.example.org/code/repo", response["url"])

assert.IsType(t, []interface{}{}, response["aliases"])

aliases := response["aliases"].([]interface{})
assert.Equal(t, 1, len(aliases))

assert.Equal(t, "https://18-b.example.org/code/repo", aliases[0])

assert.Equal(t, "-", response["publiccodeYml"])
assert.Equal(t, "80,90,99", response["vitality"])

match, err := regexp.MatchString(UUID_REGEXP, response["id"].(string))
assert.Nil(t, err)
assert.True(t, match)

created, err := time.Parse(time.RFC3339, response["createdAt"].(string))
assert.Nil(t, err)

updated, err := time.Parse(time.RFC3339, response["updatedAt"].(string))
assert.Nil(t, err)

assert.Greater(t, updated, created)
},
},
{
description: "PATCH a software resource with JSON Patch - replace",
query: "PATCH /v1/software/59803fb7-8eec-4fe5-a354-8926009c364a",
Expand Down Expand Up @@ -2112,6 +2213,42 @@ func TestSoftwareEndpoints(t *testing.T) {
assert.Greater(t, updated, created)
},
},
{
description: "PATCH a software resource with JSON Patch - replace vitality",
query: "PATCH /v1/software/59803fb7-8eec-4fe5-a354-8926009c364a",
body: `[{"op": "replace", "path": "/vitality", "value": "10,11"}]`,
headers: map[string][]string{
"Authorization": {goodToken},
"Content-Type": {"application/json-patch+json"},
},

expectedCode: 200,
expectedContentType: "application/json",
validateFunc: func(t *testing.T, response map[string]interface{}) {
assert.Equal(t, true, response["active"])
assert.Equal(t, "https://18-a.example.org/code/repo", response["url"])
assert.Equal(t, "10,11", response["vitality"])

assert.IsType(t, []interface{}{}, response["aliases"])

aliases := response["aliases"].([]interface{})
assert.Equal(t, 1, len(aliases))

assert.Equal(t, "https://18-b.example.org/code/repo", aliases[0])

assert.Equal(t, "-", response["publiccodeYml"])
assert.Equal(t, "59803fb7-8eec-4fe5-a354-8926009c364a", response["id"])

created, err := time.Parse(time.RFC3339, response["createdAt"].(string))
assert.Nil(t, err)

updated, err := time.Parse(time.RFC3339, response["updatedAt"].(string))
assert.Nil(t, err)

assert.Greater(t, updated, created)
},
},

{
description: "PATCH a software resource with JSON Patch - add",
query: "PATCH /v1/software/59803fb7-8eec-4fe5-a354-8926009c364a",
Expand Down
1 change: 1 addition & 0 deletions test/testdata/fixtures/software.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- id: 9f135268-a37e-4ead-96ec-e4a24bb9344a
publiccode_yml: "-"
software_url_id: f22d408f-93a5-411c-9c35-99039514afc4
vitality: "90,85,90,90"
created_at: '2014-05-16T00:00:00+00:00'
updated_at: '2014-05-16T00:00:00+00:00'
- id: 18348f13-1076-4a1e-b204-ed541b824d64
Expand Down

0 comments on commit 42f0820

Please sign in to comment.