• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Permify / permify / 10584372539

27 Aug 2024 07:11PM UTC coverage: 79.671% (-0.04%) from 79.709%
10584372539

push

github

web-flow
Merge pull request #1524 from canack/general-fixes

refactor: improve efficiency, Bug fixes, Code readability

46 of 52 new or added lines in 7 files covered. (88.46%)

4 existing lines in 1 file now uncovered.

8050 of 10104 relevant lines covered (79.67%)

113.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.57
/pkg/development/coverage/coverage.go
1
package coverage
2

3
import (
4
        "fmt"
5

6
        "golang.org/x/exp/slices"
7

8
        "github.com/Permify/permify/pkg/attribute"
9
        "github.com/Permify/permify/pkg/development/file"
10
        "github.com/Permify/permify/pkg/dsl/compiler"
11
        "github.com/Permify/permify/pkg/dsl/parser"
12
        base "github.com/Permify/permify/pkg/pb/base/v1"
13
        "github.com/Permify/permify/pkg/tuple"
14
)
15

16
// SchemaCoverageInfo - Schema coverage info
17
type SchemaCoverageInfo struct {
18
        EntityCoverageInfo         []EntityCoverageInfo
19
        TotalRelationshipsCoverage int
20
        TotalAttributesCoverage    int
21
        TotalAssertionsCoverage    int
22
}
23

24
// EntityCoverageInfo - Entity coverage info
25
type EntityCoverageInfo struct {
26
        EntityName string
27

28
        UncoveredRelationships       []string
29
        CoverageRelationshipsPercent int
30

31
        UncoveredAttributes       []string
32
        CoverageAttributesPercent int
33

34
        UncoveredAssertions       map[string][]string
35
        CoverageAssertionsPercent map[string]int
36
}
37

38
// SchemaCoverage
39
//
40
// schema:
41
//
42
//        entity user {}
43
//
44
//        entity organization {
45
//            // organizational roles
46
//            relation admin @user
47
//            relation member @user
48
//        }
49
//
50
//        entity repository {
51
//            // represents repositories parent organization
52
//            relation parent @organization
53
//
54
//            // represents owner of this repository
55
//            relation owner  @user @organization#admin
56
//
57
//            // permissions
58
//            permission edit   = parent.admin or owner
59
//            permission delete = owner
60
//        }
61
//
62
// - relationships coverage
63
//
64
// organization#admin@user
65
// organization#member@user
66
// repository#parent@organization
67
// repository#owner@user
68
// repository#owner@organization#admin
69
//
70
// - assertions coverage
71
//
72
// repository#edit
73
// repository#delete
74
type SchemaCoverage struct {
75
        EntityName    string
76
        Relationships []string
77
        Attributes    []string
78
        Assertions    []string
79
}
80

81
func Run(shape file.Shape) SchemaCoverageInfo {
3✔
82
        p, err := parser.NewParser(shape.Schema).Parse()
3✔
83
        if err != nil {
3✔
84
                return SchemaCoverageInfo{}
×
85
        }
×
86

87
        definitions, _, err := compiler.NewCompiler(true, p).Compile()
3✔
88
        if err != nil {
3✔
89
                return SchemaCoverageInfo{}
×
90
        }
×
91

92
        schemaCoverageInfo := SchemaCoverageInfo{}
3✔
93

3✔
94
        var refs = make([]SchemaCoverage, len(definitions))
3✔
95
        for i, en := range definitions {
19✔
96
                refs[i] = references(en)
16✔
97
        }
16✔
98

99
        // Iterate through the schema coverage references
100
        for _, ref := range refs {
19✔
101
                // Initialize EntityCoverageInfo for the current entity
16✔
102
                entityCoverageInfo := EntityCoverageInfo{
16✔
103
                        EntityName:                   ref.EntityName,
16✔
104
                        UncoveredRelationships:       []string{},
16✔
105
                        UncoveredAttributes:          []string{},
16✔
106
                        CoverageAssertionsPercent:    map[string]int{},
16✔
107
                        UncoveredAssertions:          map[string][]string{},
16✔
108
                        CoverageRelationshipsPercent: 0,
16✔
109
                        CoverageAttributesPercent:    0,
16✔
110
                }
16✔
111

16✔
112
                // Calculate relationships coverage
16✔
113
                er := relationships(ref.EntityName, shape.Relationships)
16✔
114

16✔
115
                for _, relationship := range ref.Relationships {
55✔
116
                        if !slices.Contains(er, relationship) {
53✔
117
                                entityCoverageInfo.UncoveredRelationships = append(entityCoverageInfo.UncoveredRelationships, relationship)
14✔
118
                        }
14✔
119
                }
120

121
                entityCoverageInfo.CoverageRelationshipsPercent = calculateCoveragePercent(
16✔
122
                        ref.Relationships,
16✔
123
                        entityCoverageInfo.UncoveredRelationships,
16✔
124
                )
16✔
125

16✔
126
                // Calculate attributes coverage
16✔
127
                at := attributes(ref.EntityName, shape.Attributes)
16✔
128

16✔
129
                for _, attr := range ref.Attributes {
16✔
130
                        if !slices.Contains(at, attr) {
×
131
                                entityCoverageInfo.UncoveredAttributes = append(entityCoverageInfo.UncoveredAttributes, attr)
×
132
                        }
×
133
                }
134

135
                entityCoverageInfo.CoverageAttributesPercent = calculateCoveragePercent(
16✔
136
                        ref.Attributes,
16✔
137
                        entityCoverageInfo.UncoveredAttributes,
16✔
138
                )
16✔
139

16✔
140
                // Calculate assertions coverage for each scenario
16✔
141
                for _, s := range shape.Scenarios {
35✔
142
                        ca := assertions(ref.EntityName, s.Checks, s.EntityFilters)
19✔
143

19✔
144
                        for _, assertion := range ref.Assertions {
57✔
145
                                if !slices.Contains(ca, assertion) {
69✔
146
                                        entityCoverageInfo.UncoveredAssertions[s.Name] = append(entityCoverageInfo.UncoveredAssertions[s.Name], assertion)
31✔
147
                                }
31✔
148
                        }
149

150
                        entityCoverageInfo.CoverageAssertionsPercent[s.Name] = calculateCoveragePercent(
19✔
151
                                ref.Assertions,
19✔
152
                                entityCoverageInfo.UncoveredAssertions[s.Name],
19✔
153
                        )
19✔
154
                }
155

156
                schemaCoverageInfo.EntityCoverageInfo = append(schemaCoverageInfo.EntityCoverageInfo, entityCoverageInfo)
16✔
157
        }
158

159
        // Calculate and assign the total relationships and assertions coverage to the schemaCoverageInfo
160
        relationshipsCoverage, attributesCoverage, assertionsCoverage := calculateTotalCoverage(schemaCoverageInfo.EntityCoverageInfo)
3✔
161
        schemaCoverageInfo.TotalRelationshipsCoverage = relationshipsCoverage
3✔
162
        schemaCoverageInfo.TotalAttributesCoverage = attributesCoverage
3✔
163
        schemaCoverageInfo.TotalAssertionsCoverage = assertionsCoverage
3✔
164

3✔
165
        return schemaCoverageInfo
3✔
166
}
167

168
// calculateCoveragePercent - Calculate coverage percentage based on total and uncovered elements
169
func calculateCoveragePercent(totalElements, uncoveredElements []string) int {
51✔
170
        coveragePercent := 100
51✔
171
        totalCount := len(totalElements)
51✔
172

51✔
173
        if totalCount != 0 {
74✔
174
                coveredCount := totalCount - len(uncoveredElements)
23✔
175
                coveragePercent = (coveredCount * 100) / totalCount
23✔
176
        }
23✔
177

178
        return coveragePercent
51✔
179
}
180

181
// calculateTotalCoverage - Calculate total relationships and assertions coverage
182
func calculateTotalCoverage(entities []EntityCoverageInfo) (int, int, int) {
3✔
183
        totalRelationships := 0
3✔
184
        totalCoveredRelationships := 0
3✔
185
        totalAttributes := 0
3✔
186
        totalCoveredAttributes := 0
3✔
187
        totalAssertions := 0
3✔
188
        totalCoveredAssertions := 0
3✔
189

3✔
190
        // Iterate over each entity in the list
3✔
191
        for _, entity := range entities {
19✔
192
                totalRelationships++
16✔
193
                totalCoveredRelationships += entity.CoverageRelationshipsPercent
16✔
194

16✔
195
                totalAttributes++
16✔
196
                totalCoveredAttributes += entity.CoverageAttributesPercent
16✔
197

16✔
198
                for _, assertionsPercent := range entity.CoverageAssertionsPercent {
35✔
199
                        totalAssertions++
19✔
200
                        totalCoveredAssertions += assertionsPercent
19✔
201
                }
19✔
202
        }
203

204
        // Calculate the coverage percentages
205
        totalRelationshipsCoverage := totalCoveredRelationships / totalRelationships
3✔
206
        totalAttributesCoverage := totalCoveredAttributes / totalAttributes
3✔
207
        totalAssertionsCoverage := totalCoveredAssertions / totalAssertions
3✔
208

3✔
209
        // Return the coverage percentages
3✔
210
        return totalRelationshipsCoverage, totalAttributesCoverage, totalAssertionsCoverage
3✔
211
}
212

213
// References - Get references for a given entity
214
func references(entity *base.EntityDefinition) (coverage SchemaCoverage) {
16✔
215
        // Set the entity name in the coverage struct
16✔
216
        coverage.EntityName = entity.GetName()
16✔
217
        // Iterate over all relations in the entity
16✔
218
        for _, relation := range entity.GetRelations() {
43✔
219
                // Iterate over all references within each relation
27✔
220
                for _, reference := range relation.GetRelationReferences() {
66✔
221
                        if reference.GetRelation() != "" {
50✔
222
                                // Format and append the relationship to the coverage struct
11✔
223
                                formattedRelationship := fmt.Sprintf("%s#%s@%s#%s", entity.GetName(), relation.GetName(), reference.GetType(), reference.GetRelation())
11✔
224
                                coverage.Relationships = append(coverage.Relationships, formattedRelationship)
11✔
225
                        } else {
39✔
226
                                formattedRelationship := fmt.Sprintf("%s#%s@%s", entity.GetName(), relation.GetName(), reference.GetType())
28✔
227
                                coverage.Relationships = append(coverage.Relationships, formattedRelationship)
28✔
228
                        }
28✔
229
                }
230
        }
231
        // Iterate over all attributes in the entity
232
        for _, attr := range entity.GetAttributes() {
16✔
233
                // Format and append the attribute to the coverage struct
×
234
                formattedAttribute := fmt.Sprintf("%s#%s", entity.GetName(), attr.GetName())
×
235
                coverage.Attributes = append(coverage.Attributes, formattedAttribute)
×
236
        }
×
237
        // Iterate over all permissions in the entity
238
        for _, permission := range entity.GetPermissions() {
52✔
239
                // Format and append the permission to the coverage struct
36✔
240
                formattedPermission := fmt.Sprintf("%s#%s", entity.GetName(), permission.GetName())
36✔
241
                coverage.Assertions = append(coverage.Assertions, formattedPermission)
36✔
242
        }
36✔
243
        // Return the coverage struct
244
        return
16✔
245
}
246

247
// relationships - Get relationships for a given entity
248
func relationships(en string, relationships []string) []string {
16✔
249
        var rels []string
16✔
250
        for _, relationship := range relationships {
322✔
251
                tup, err := tuple.Tuple(relationship)
306✔
252
                if err != nil {
306✔
253
                        return []string{}
×
254
                }
×
255
                if tup.GetEntity().GetType() != en {
565✔
256
                        continue
259✔
257
                }
258
                // Check if the reference has a relation name
259
                if tup.GetSubject().GetRelation() != "" {
55✔
260
                        // Format and append the relationship to the coverage struct
8✔
261
                        rels = append(rels, fmt.Sprintf("%s#%s@%s#%s", tup.GetEntity().GetType(), tup.GetRelation(), tup.GetSubject().GetType(), tup.GetSubject().GetRelation()))
8✔
262
                } else {
47✔
263
                        rels = append(rels, fmt.Sprintf("%s#%s@%s", tup.GetEntity().GetType(), tup.GetRelation(), tup.GetSubject().GetType()))
39✔
264
                }
39✔
265
                // Format ad append the relationship without the relation name to the coverage struct
266
        }
267
        return rels
16✔
268
}
269

270
// attributes - Get attributes for a given entity
271
func attributes(en string, attributes []string) []string {
16✔
272
        var attrs = make([]string, len(attributes))
16✔
273
        for i, attr := range attributes {
16✔
274
                a, err := attribute.Attribute(attr)
×
275
                if err != nil {
×
276
                        return []string{}
×
277
                }
×
278
                if a.GetEntity().GetType() != en {
×
279
                        continue
×
280
                }
NEW
281
                attrs[i] = fmt.Sprintf("%s#%s", a.GetEntity().GetType(), a.GetAttribute())
×
282
        }
283
        return attrs
16✔
284
}
285

286
// assertions - Get assertions for a given entity
287
func assertions(en string, checks []file.Check, filters []file.EntityFilter) []string {
19✔
288
        // Initialize an empty slice to store the resulting assertions
19✔
289
        var asrts []string
19✔
290

19✔
291
        // Iterate over each check in the checks slice
19✔
292
        for _, assertion := range checks {
55✔
293
                // Get the corresponding entity object for the current assertion
36✔
294
                ca, err := tuple.E(assertion.Entity)
36✔
295
                if err != nil {
36✔
296
                        // If there's an error, return an empty slice
×
297
                        return []string{}
×
298
                }
×
299

300
                // If the current entity type doesn't match the given entity type, continue to the next check
301
                if ca.GetType() != en {
65✔
302
                        continue
29✔
303
                }
304

305
                // Iterate over the keys (permissions) in the Assertions map
306
                for permission := range assertion.Assertions {
15✔
307
                        // Append the formatted permission string to the asrts slice
8✔
308
                        asrts = append(asrts, fmt.Sprintf("%s#%s", ca.GetType(), permission))
8✔
309
                }
8✔
310
        }
311

312
        // Iterate over each entity filter in the filters slice
313
        for _, assertion := range filters {
26✔
314
                // If the current entity type doesn't match the given entity type, continue to the next filter
7✔
315
                if assertion.EntityType != en {
12✔
316
                        continue
5✔
317
                }
318

319
                // Iterate over the keys (permissions) in the Assertions map
320
                for permission := range assertion.Assertions {
4✔
321
                        // Append the formatted permission string to the asrts slice
2✔
322
                        asrts = append(asrts, fmt.Sprintf("%s#%s", assertion.EntityType, permission))
2✔
323
                }
2✔
324
        }
325

326
        // Return the asrts slice containing the collected assertions
327
        return asrts
19✔
328
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc