@@ -16,10 +16,14 @@ package services
1616
1717import (
1818 "context"
19+ "encoding/json"
1920 "net/http"
2021 "net/http/httptest"
22+ "net/url"
2123 "testing"
24+ "time"
2225
26+ "github.com/google/go-github/v62/github"
2327 "github.com/google/uuid"
2428 "github.com/l3montree-dev/devguard/database/models"
2529 "github.com/l3montree-dev/devguard/dtos"
@@ -291,3 +295,149 @@ func TestFetchSbomsFromUpstream_PassesURLNotRef(t *testing.T) {
291295 assert .Equal (t , sbomURL , invalidURLs [0 ].URL )
292296 })
293297}
298+
299+ func TestFetchOpenVexFromGitHub (t * testing.T ) {
300+ originalNewGitHubClient := newGitHubClient
301+ originalDownloadRawFileFn := downloadRawFileFn
302+ t .Cleanup (func () {
303+ newGitHubClient = originalNewGitHubClient
304+ downloadRawFileFn = originalDownloadRawFileFn
305+ })
306+
307+ t .Run ("should fetch openvex reports from json files in the repository" , func (t * testing.T ) {
308+ mockGitHub := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
309+ switch {
310+ case r .Method == http .MethodGet && r .URL .Path == "/repos/octo-org/openvex-repo" :
311+ _ , _ = w .Write ([]byte (`{"default_branch":"main"}` ))
312+ case r .Method == http .MethodGet && r .URL .Path == "/repos/octo-org/openvex-repo/git/trees/main" :
313+ if got := r .URL .Query ().Get ("recursive" ); got != "1" {
314+ t .Fatalf ("expected recursive=1, got %q" , got )
315+ }
316+ _ , _ = w .Write ([]byte (`{"tree":[{"path":"reports/openvex.json","type":"blob"},{"path":"README.md","type":"blob"}]}` ))
317+ default :
318+ t .Fatalf ("unexpected github api request: %s %s" , r .Method , r .URL .String ())
319+ }
320+ }))
321+ defer mockGitHub .Close ()
322+
323+ newGitHubClient = func () * github.Client {
324+ client := github .NewClient (mockGitHub .Client ())
325+ baseURL , err := url .Parse (mockGitHub .URL + "/" )
326+ if err != nil {
327+ t .Fatalf ("failed to parse mock github url: %v" , err )
328+ }
329+ client .BaseURL = baseURL
330+ client .UploadURL = baseURL
331+ return client
332+ }
333+
334+ calls := 0
335+ downloadRawFileFn = func (owner , repo , branch , filePath string ) ([]byte , error ) {
336+ calls ++
337+ assert .Equal (t , "octo-org" , owner )
338+ assert .Equal (t , "openvex-repo" , repo )
339+ assert .Equal (t , "main" , branch )
340+ assert .Equal (t , "reports/openvex.json" , filePath )
341+
342+ ts := time .Date (2026 , time .May , 20 , 12 , 0 , 0 , 0 , time .UTC )
343+ payload := map [string ]any {
344+ "@context" : "https://openvex.dev/ns/v0.2.0" ,
345+ "@id" : "openvex-1" ,
346+ "author" : "test-author" ,
347+ "timestamp" : ts ,
348+ "version" : 1 ,
349+ "statements" : []any {},
350+ }
351+ return json .Marshal (payload )
352+ }
353+
354+ service := & scanService {}
355+ reports , err := service .FetchOpenVexFromGitHub (context .Background (), "https://github.com/octo-org/openvex-repo" )
356+ assert .NoError (t , err )
357+ assert .Len (t , reports , 1 )
358+ assert .Equal (t , "https://github.com/octo-org/openvex-repo" , reports [0 ].Source )
359+ assert .Equal (t , "openvex-1" , reports [0 ].Report .ID )
360+ assert .Equal (t , "test-author" , reports [0 ].Report .Author )
361+ assert .Equal (t , 1 , reports [0 ].Report .Version )
362+ assert .Equal (t , 1 , calls )
363+ })
364+
365+ t .Run ("should fetch multiple openvex reports from multiple json files" , func (t * testing.T ) {
366+ mockGitHub := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
367+ switch {
368+ case r .Method == http .MethodGet && r .URL .Path == "/repos/octo-org/multi-vex-repo" :
369+ _ , _ = w .Write ([]byte (`{"default_branch":"develop"}` ))
370+ case r .Method == http .MethodGet && r .URL .Path == "/repos/octo-org/multi-vex-repo/git/trees/develop" :
371+ if got := r .URL .Query ().Get ("recursive" ); got != "1" {
372+ t .Fatalf ("expected recursive=1, got %q" , got )
373+ }
374+ _ , _ = w .Write ([]byte (`{"tree":[{"path":"vex/vex1.json","type":"blob"},{"path":"vex/vex2.json","type":"blob"},{"path":"README.md","type":"blob"}]}` ))
375+ default :
376+ t .Fatalf ("unexpected github api request: %s %s" , r .Method , r .URL .String ())
377+ }
378+ }))
379+ defer mockGitHub .Close ()
380+
381+ newGitHubClient = func () * github.Client {
382+ client := github .NewClient (mockGitHub .Client ())
383+ baseURL , err := url .Parse (mockGitHub .URL + "/" )
384+ if err != nil {
385+ t .Fatalf ("failed to parse mock github url: %v" , err )
386+ }
387+ client .BaseURL = baseURL
388+ client .UploadURL = baseURL
389+ return client
390+ }
391+
392+ calls := 0
393+ downloadRawFileFn = func (owner , repo , branch , filePath string ) ([]byte , error ) {
394+ calls ++
395+ assert .Equal (t , "octo-org" , owner )
396+ assert .Equal (t , "multi-vex-repo" , repo )
397+ assert .Equal (t , "develop" , branch )
398+
399+ ts := time .Date (2026 , time .May , 20 , 12 , 0 , 0 , 0 , time .UTC )
400+ var id , author string
401+
402+ if filePath == "vex/vex1.json" {
403+ id = "openvex-first"
404+ author = "author-one"
405+ } else if filePath == "vex/vex2.json" {
406+ id = "openvex-second"
407+ author = "author-two"
408+ } else {
409+ t .Fatalf ("unexpected file path: %s" , filePath )
410+ }
411+
412+ payload := map [string ]any {
413+ "@context" : "https://openvex.dev/ns/v0.2.0" ,
414+ "@id" : id ,
415+ "author" : author ,
416+ "timestamp" : ts ,
417+ "version" : 1 ,
418+ "statements" : []any {},
419+ }
420+ return json .Marshal (payload )
421+ }
422+
423+ service := & scanService {}
424+ reports , err := service .FetchOpenVexFromGitHub (context .Background (), "https://github.com/octo-org/multi-vex-repo" )
425+ assert .NoError (t , err )
426+ assert .Len (t , reports , 2 )
427+ assert .Equal (t , "https://github.com/octo-org/multi-vex-repo" , reports [0 ].Source )
428+ assert .Equal (t , "https://github.com/octo-org/multi-vex-repo" , reports [1 ].Source )
429+ assert .Equal (t , "openvex-first" , reports [0 ].Report .ID )
430+ assert .Equal (t , "openvex-second" , reports [1 ].Report .ID )
431+ assert .Equal (t , "author-one" , reports [0 ].Report .Author )
432+ assert .Equal (t , "author-two" , reports [1 ].Report .Author )
433+ assert .Equal (t , 2 , calls )
434+ })
435+
436+ t .Run ("should reject non github urls" , func (t * testing.T ) {
437+ service := & scanService {}
438+ reports , err := service .FetchOpenVexFromGitHub (context .Background (), "https://example.com/repo" )
439+ assert .Error (t , err )
440+ assert .Nil (t , reports )
441+ assert .Contains (t , err .Error (), "invalid github repository url" )
442+ })
443+ }
0 commit comments