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

Permify / permify / 18341319825

08 Oct 2025 10:13AM UTC coverage: 86.488% (+0.3%) from 86.148%
18341319825

Pull #2532

github

tolgaozen
refactor: clean up imports and comments in various files
Pull Request #2532: refactor: clean up imports and comments in various files

50 of 57 new or added lines in 4 files covered. (87.72%)

1 existing line in 1 file now uncovered.

9326 of 10783 relevant lines covered (86.49%)

205.24 hits per line

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

87.22
/pkg/development/coverage/coverage.go
1
package coverage // Coverage analysis package
2
import (         // Package imports
3
        "fmt"    // Formatting
4
        "slices" // Slice operations
5

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

14
// SchemaCoverageInfo - Schema coverage info
15
type SchemaCoverageInfo struct {
16
        EntityCoverageInfo         []EntityCoverageInfo // Entity coverage details
17
        TotalRelationshipsCoverage int                  // Total relationships coverage
18
        TotalAttributesCoverage    int                  // Total attributes coverage
19
        TotalAssertionsCoverage    int                  // Total assertions coverage
20
} // End SchemaCoverageInfo
21

22
// EntityCoverageInfo - Entity coverage info
23
type EntityCoverageInfo struct {
24
        EntityName string
25

26
        UncoveredRelationships       []string
27
        CoverageRelationshipsPercent int
28

29
        UncoveredAttributes       []string
30
        CoverageAttributesPercent int
31

32
        UncoveredAssertions       map[string][]string
33
        CoverageAssertionsPercent map[string]int
34
}
35

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

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

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

90
        schemaCoverageInfo := SchemaCoverageInfo{}
3✔
91

3✔
92
        refs := make([]SchemaCoverage, len(definitions))
3✔
93
        for idx, entityDef := range definitions { // Build entity references
19✔
94
                refs[idx] = references(entityDef) // Extract references
16✔
95
        } // References built
16✔
96

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

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

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

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

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

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

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

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

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

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

154
                schemaCoverageInfo.EntityCoverageInfo = append(schemaCoverageInfo.EntityCoverageInfo, entityCoverageInfo)
16✔
155
        }
156

157
        // Calculate total coverage for relationships, attributes and assertions
158
        relationshipsCoverage, attributesCoverage, assertionsCoverage := calculateTotalCoverage(schemaCoverageInfo.EntityCoverageInfo) // Calculate totals
3✔
159
        schemaCoverageInfo.TotalRelationshipsCoverage = relationshipsCoverage                                                          // Set total relationships
3✔
160
        schemaCoverageInfo.TotalAttributesCoverage = attributesCoverage                                                                // Set total attributes
3✔
161
        schemaCoverageInfo.TotalAssertionsCoverage = assertionsCoverage                                                                // Set total assertions
3✔
162
        return schemaCoverageInfo                                                                                                      // Return coverage info
3✔
163
}
164

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

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

175
        return coveragePercent
51✔
176
}
177

178
// calculateTotalCoverage - Calculate total relationships and assertions coverage
179
func calculateTotalCoverage(entities []EntityCoverageInfo) (int, int, int) {
3✔
180
        totalRelationships := 0        // Total relationships counter
3✔
181
        totalCoveredRelationships := 0 // Covered relationships counter
3✔
182
        totalAttributes := 0           // Total attributes counter
3✔
183
        totalCoveredAttributes := 0    // Covered attributes counter
3✔
184
        totalAssertions := 0           // Total assertions counter
3✔
185
        totalCoveredAssertions := 0    // Covered assertions counter
3✔
186
        // Process all entities to calculate coverage
3✔
187
        for _, entity := range entities { // Process each entity
19✔
188
                totalRelationships++                                                // Count relationships
16✔
189
                totalCoveredRelationships += entity.CoverageRelationshipsPercent    // Add covered
16✔
190
                totalAttributes++                                                   // Count attributes
16✔
191
                totalCoveredAttributes += entity.CoverageAttributesPercent          // Add covered attributes
16✔
192
                for _, assertionPercent := range entity.CoverageAssertionsPercent { // Process assertions
35✔
193
                        totalAssertions++                          // Increment assertion count
19✔
194
                        totalCoveredAssertions += assertionPercent // Add covered assertion
19✔
195
                } // Assertions processed
19✔
196
        } // Entities processed
197
        // Calculate average coverage percentages for all entities
198
        totalRelationshipsCoverage := totalCoveredRelationships / totalRelationships
3✔
199
        totalAttributesCoverage := totalCoveredAttributes / totalAttributes
3✔
200
        totalAssertionsCoverage := totalCoveredAssertions / totalAssertions
3✔
201
        // Return calculated coverage percentages
3✔
202
        return totalRelationshipsCoverage, totalAttributesCoverage, totalAssertionsCoverage // Return totals
3✔
203
} // End calculateTotalCoverage
204
// References - Get references for a given entity
205
func references(entity *base.EntityDefinition) (coverage SchemaCoverage) {
16✔
206
        // Set the entity name in the coverage struct
16✔
207
        coverage.EntityName = entity.GetName()
16✔
208
        // Iterate over all relations in the entity
16✔
209
        for _, relation := range entity.GetRelations() {
43✔
210
                // Iterate over all references within each relation
27✔
211
                for _, reference := range relation.GetRelationReferences() {
66✔
212
                        if reference.GetRelation() != "" {
50✔
213
                                // Format and append the relationship to the coverage struct
11✔
214
                                formattedRelationship := fmt.Sprintf("%s#%s@%s#%s", entity.GetName(), relation.GetName(), reference.GetType(), reference.GetRelation())
11✔
215
                                coverage.Relationships = append(coverage.Relationships, formattedRelationship)
11✔
216
                        } else {
39✔
217
                                formattedRelationship := fmt.Sprintf("%s#%s@%s", entity.GetName(), relation.GetName(), reference.GetType())
28✔
218
                                coverage.Relationships = append(coverage.Relationships, formattedRelationship)
28✔
219
                        }
28✔
220
                }
221
        }
222
        // Iterate over all attributes in the entity
223
        for _, attr := range entity.GetAttributes() {
16✔
224
                // Format and append the attribute to the coverage struct
×
225
                formattedAttribute := fmt.Sprintf("%s#%s", entity.GetName(), attr.GetName())
×
226
                coverage.Attributes = append(coverage.Attributes, formattedAttribute)
×
227
        }
×
228
        // Iterate over all permissions in the entity
229
        for _, permission := range entity.GetPermissions() {
52✔
230
                // Format and append the permission to the coverage struct
36✔
231
                formattedPermission := fmt.Sprintf("%s#%s", entity.GetName(), permission.GetName())
36✔
232
                coverage.Assertions = append(coverage.Assertions, formattedPermission)
36✔
233
        }
36✔
234
        // Return the coverage struct
235
        return
16✔
236
}
237

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

261
// attributes - Get attributes for a given entity
262
func attributes(en string, attributes []string) []string {
16✔
263
        attrs := make([]string, len(attributes))
16✔
264
        for index, attrStr := range attributes { // Iterate attribute strings
16✔
NEW
265
                a, err := attribute.Attribute(attrStr)
×
266
                if err != nil {
×
267
                        return []string{}
×
268
                }
×
269
                if a.GetEntity().GetType() != en {
×
270
                        continue
×
271
                }
NEW
272
                attrs[index] = fmt.Sprintf("%s#%s", a.GetEntity().GetType(), a.GetAttribute()) // Format attribute
×
273
        } // End iteration
274
        return attrs // Return attributes
16✔
275
} // End attributes
276

277
// assertions - Get assertions for a given entity
278
func assertions(en string, checks []file.Check, filters []file.EntityFilter) []string {
19✔
279
        // Initialize an empty slice to store the resulting assertions
19✔
280
        var asrts []string
19✔
281

19✔
282
        // Iterate over each check in the checks slice
19✔
283
        for _, assertion := range checks {
55✔
284
                // Get the corresponding entity object for the current assertion
36✔
285
                ca, err := tuple.E(assertion.Entity)
36✔
286
                if err != nil {
36✔
287
                        // If there's an error, return an empty slice
×
288
                        return []string{}
×
289
                }
×
290

291
                // If the current entity type doesn't match the given entity type, continue to the next check
292
                if ca.GetType() != en {
65✔
293
                        continue
29✔
294
                }
295

296
                // Iterate over the keys (permissions) in the Assertions map
297
                for permission := range assertion.Assertions {
15✔
298
                        // Append the formatted permission string to the asrts slice
8✔
299
                        asrts = append(asrts, fmt.Sprintf("%s#%s", ca.GetType(), permission))
8✔
300
                }
8✔
301
        }
302

303
        // Iterate over each entity filter in the filters slice
304
        for _, assertion := range filters {
26✔
305
                // If the current entity type doesn't match the given entity type, continue to the next filter
7✔
306
                if assertion.EntityType != en {
12✔
307
                        continue
5✔
308
                }
309

310
                // Iterate over the keys (permissions) in the Assertions map
311
                for permission := range assertion.Assertions {
4✔
312
                        // Append the formatted permission string to the asrts slice
2✔
313
                        asrts = append(asrts, fmt.Sprintf("%s#%s", assertion.EntityType, permission))
2✔
314
                }
2✔
315
        }
316

317
        // Return the asrts slice containing the collected assertions
318
        return asrts
19✔
319
}
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