Skip to content

Commit b42bcbc

Browse files
updates test runs get api
1 parent c27eca5 commit b42bcbc

File tree

7 files changed

+329
-17
lines changed

7 files changed

+329
-17
lines changed

main.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package main
22

33
import (
4+
"github.com/guidewire/fern-reporter/pkg/datamigrations"
5+
"strings"
6+
47
"github.com/99designs/gqlgen/graphql/handler/transport"
58
"github.com/99designs/gqlgen/graphql/playground"
69
"github.com/google/uuid"
@@ -11,13 +14,14 @@ import (
1114
"gorm.io/gorm"
1215

1316
"context"
17+
"html/template"
18+
"log"
19+
1420
"github.com/99designs/gqlgen/graphql/handler"
1521
"github.com/guidewire/fern-reporter/config"
1622
"github.com/guidewire/fern-reporter/pkg/api/routers"
1723
"github.com/guidewire/fern-reporter/pkg/auth"
1824
"github.com/guidewire/fern-reporter/pkg/db"
19-
"html/template"
20-
"log"
2125

2226
"time"
2327

@@ -45,6 +49,8 @@ func initConfig() {
4549

4650
func initDb() {
4751
db.Initialize()
52+
db.GetDb().Debug()
53+
go datamigrations.BackfillTestRunStatus(db.GetDb())
4854
}
4955

5056
func initServer() {
@@ -63,10 +69,10 @@ func initServer() {
6369
}
6470

6571
router.Use(cors.New(cors.Config{
66-
AllowMethods: []string{"GET", "POST"},
67-
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "ACCESS_TOKEN"},
68-
AllowCredentials: false,
69-
AllowAllOrigins: true,
72+
AllowMethods: []string{"GET", "POST", "DELETE", "PUT"},
73+
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "ACCESS_TOKEN", "User-Agent"},
74+
AllowCredentials: true,
75+
AllowOriginFunc: isAllowedOrigin,
7076
MaxAge: 12 * time.Hour,
7177
}))
7278

@@ -133,6 +139,15 @@ func configJWTMiddleware(router *gin.Engine) {
133139
log.Println("JWT Middleware configured successfully.")
134140
}
135141

142+
func isAllowedOrigin(origin string) bool {
143+
origin = strings.ToLower(origin)
144+
145+
if strings.Contains(origin, "localhost") || strings.HasPrefix(origin, "https://fern") {
146+
return true
147+
}
148+
return false
149+
}
150+
136151
func SetMiddlewareCookie() gin.HandlerFunc {
137152
return func(c *gin.Context) {
138153
_, err := c.Cookie(utils.CookieName)

pkg/api/handlers/handler_utils.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,43 @@ func ParseTimeFromStringWithDefault(timeString string, defaultTime time.Time) (t
5252

5353
func GetProjectSpecStatistics(h *Handler, projectId string) []models.TestSummary {
5454
var testSummaries []models.TestSummary
55+
//h.db.Table("test_runs").
56+
// Joins("INNER JOIN suite_runs ON test_runs.id = suite_runs.test_run_id").
57+
// Joins("INNER JOIN spec_runs ON suite_runs.id = spec_runs.suite_id").
58+
// Select(`suite_runs.id AS suite_run_id,
59+
// suite_runs.suite_name,
60+
// test_runs.start_time,
61+
// COUNT(spec_runs.id) FILTER (WHERE spec_runs.status = 'PASSED') AS total_passed_spec_runs,
62+
// COUNT(spec_runs.id) FILTER (WHERE spec_runs.status = 'SKIPPED') AS total_skipped_spec_runs,
63+
// COUNT(spec_runs.id) AS total_spec_runs`).
64+
// Where("test_runs.project_id = ?", projectId).
65+
// Group("suite_runs.id, test_runs.start_time").
66+
// Order("test_runs.start_time").
67+
// Scan(&testSummaries)
68+
69+
//h.db.Table("test_runs").
70+
// Joins("INNER JOIN suite_runs ON test_runs.id = suite_runs.test_run_id").
71+
// Joins("INNER JOIN spec_runs ON suite_runs.id = spec_runs.suite_id").
72+
// Select(`suite_runs.id AS suite_run_id,
73+
// suite_runs.suite_name,
74+
// test_runs.start_time,
75+
// COUNT(spec_runs.id) FILTER (WHERE spec_runs.status = 'passed') AS total_passed_spec_runs,
76+
// COUNT(spec_runs.id) FILTER (WHERE spec_runs.status = 'skipped') AS total_skipped_spec_runs,
77+
// COUNT(spec_runs.id) AS total_spec_runs`).
78+
// Where("test_runs.project_id = ?", projectId).
79+
// Group("suite_runs.id, test_runs.start_time").
80+
// Order("test_runs.start_time").
81+
// Scan(&testSummaries)
82+
5583
h.db.Table("test_runs").
5684
Joins("INNER JOIN suite_runs ON test_runs.id = suite_runs.test_run_id").
5785
Joins("INNER JOIN spec_runs ON suite_runs.id = spec_runs.suite_id").
58-
Select(`suite_runs.id AS suite_run_id,
59-
suite_runs.suite_name,
60-
test_runs.start_time,
61-
COUNT(spec_runs.id) FILTER (WHERE spec_runs.status = 'passed') AS total_passed_spec_runs,
62-
COUNT(spec_runs.id) FILTER (WHERE spec_runs.status = 'skipped') AS total_skipped_spec_runs,
63-
COUNT(spec_runs.id) AS total_spec_runs`).
86+
Select(`suite_runs.id AS suite_run_id,
87+
suite_runs.suite_name,
88+
test_runs.start_time,
89+
COUNT(spec_runs.id) FILTER (WHERE LOWER(spec_runs.status) = 'passed') AS total_passed_spec_runs,
90+
COUNT(spec_runs.id) FILTER (WHERE LOWER(spec_runs.status) = 'skipped') AS total_skipped_spec_runs,
91+
COUNT(spec_runs.id) AS total_spec_runs`).
6492
Where("test_runs.project_id = ?", projectId).
6593
Group("suite_runs.id, test_runs.start_time").
6694
Order("test_runs.start_time").

pkg/api/handlers/handlers.go

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package handlers
33
import (
44
"errors"
55
"fmt"
6+
"strings"
67

78
"github.com/guidewire/fern-reporter/config"
89
"github.com/guidewire/fern-reporter/pkg/models"
@@ -67,6 +68,8 @@ func (h *Handler) CreateTestRun(c *gin.Context) {
6768
return // Stop further processing if tag processing fails
6869
}
6970

71+
computeTestRunStatus(&testRun)
72+
7073
// Save or update the testRun record in the database
7174
if err := gdb.Save(&testRun).Error; err != nil {
7275
c.JSON(http.StatusInternalServerError, gin.H{"error": "error saving record"})
@@ -83,6 +86,24 @@ func getProjectIDByUUID(db *gorm.DB, uuid string) (uint64, error) {
8386
return project.ID, nil
8487
}
8588

89+
func computeTestRunStatus(testRun *models.TestRun) {
90+
status := "PASSED"
91+
92+
for _, suite := range testRun.SuiteRuns {
93+
for _, spec := range suite.SpecRuns {
94+
if strings.EqualFold(spec.Status, "FAILED") {
95+
testRun.Status = "FAILED"
96+
return
97+
}
98+
if strings.EqualFold(spec.Status, "SKIPPED") {
99+
status = "SKIPPED"
100+
}
101+
}
102+
}
103+
104+
testRun.Status = status
105+
}
106+
86107
func ProcessTags(db *gorm.DB, testRun *models.TestRun) error {
87108
for i, suite := range testRun.SuiteRuns {
88109
for j, spec := range suite.SpecRuns {
@@ -115,9 +136,60 @@ func ProcessTags(db *gorm.DB, testRun *models.TestRun) error {
115136
return nil
116137
}
117138

139+
//func (h *Handler) GetTestRunAll(c *gin.Context) {
140+
// var testRuns []models.TestRun
141+
// h.db.Find(&testRuns)
142+
// c.JSON(http.StatusOK, testRuns)
143+
//}
144+
118145
func (h *Handler) GetTestRunAll(c *gin.Context) {
146+
119147
var testRuns []models.TestRun
120-
h.db.Find(&testRuns)
148+
149+
projectUUID := c.Query("project_uuid")
150+
sortBy := c.DefaultQuery("sort_by", "end_time")
151+
order := c.DefaultQuery("order", "desc")
152+
fields := strings.Split(c.DefaultQuery("fields", ""), ",")
153+
154+
allowedSortFields := map[string]bool{
155+
"end_time": true,
156+
"start_time": true,
157+
"status": true,
158+
}
159+
if !allowedSortFields[sortBy] {
160+
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid sort_by field"})
161+
return
162+
}
163+
164+
if order != "asc" && order != "desc" {
165+
c.JSON(http.StatusBadRequest, gin.H{"error": "order must be 'asc' or 'desc'"})
166+
return
167+
}
168+
169+
query := h.db.Model(&models.TestRun{})
170+
171+
for _, field := range fields {
172+
switch strings.ToLower(strings.TrimSpace(field)) {
173+
case "project":
174+
query = query.Preload("Project")
175+
case "suiteruns":
176+
query = query.Preload("SuiteRuns.SpecRuns")
177+
}
178+
}
179+
180+
if projectUUID != "" {
181+
log.Println()
182+
query = query.Joins("JOIN project_details ON project_details.id = test_runs.project_id").
183+
Where("project_details.uuid = ?", projectUUID)
184+
}
185+
186+
query = query.Order(fmt.Sprintf("test_runs.%s %s", sortBy, order))
187+
188+
if err := query.Find(&testRuns).Error; err != nil {
189+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
190+
return
191+
}
192+
121193
c.JSON(http.StatusOK, testRuns)
122194
}
123195

pkg/api/handlers/handlers_test.go

Lines changed: 128 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,128 @@ var _ = Describe("Handlers", func() {
7474
Expect(testRuns[0].TestProjectName).To(Equal("project 1"))
7575
Expect(testRuns[1].TestProjectName).To(Equal("project 2"))
7676
})
77+
78+
It("should return 400 for invalid sort_by parameter", func() {
79+
w := httptest.NewRecorder()
80+
c, _ := gin.CreateTestContext(w)
81+
82+
c.Request, _ = http.NewRequest("GET", "/testruns?sort_by=invalid_field", nil)
83+
handler := handlers.NewHandler(gormDb)
84+
handler.GetTestRunAll(c)
85+
86+
Expect(w.Code).To(Equal(http.StatusBadRequest))
87+
Expect(w.Body.String()).To(ContainSubstring("invalid sort_by field"))
88+
})
89+
90+
It("should return 400 for invalid order parameter", func() {
91+
w := httptest.NewRecorder()
92+
c, _ := gin.CreateTestContext(w)
93+
94+
c.Request, _ = http.NewRequest("GET", "/testruns?order=invalid_order", nil)
95+
handler := handlers.NewHandler(gormDb)
96+
handler.GetTestRunAll(c)
97+
98+
Expect(w.Code).To(Equal(http.StatusBadRequest))
99+
Expect(w.Body.String()).To(ContainSubstring("order must be 'asc' or 'desc'"))
100+
})
101+
102+
It("should apply project_uuid filter", func() {
103+
rows := sqlmock.NewRows([]string{
104+
"id", "test_project_name", "project_id", "test_seed", "start_time", "end_time", "git_branch", "git_sha", "build_trigger_actor", "build_url", "status",
105+
}).AddRow(1, "project 1", 101, 0, time.Now(), time.Now(), "main", "abc123", "ci", "url", "PASSED")
106+
107+
mock.ExpectQuery(`(?i)SELECT .* FROM "test_runs" JOIN project_details ON project_details\.id = test_runs\.project_id WHERE project_details\.uuid = \$1 ORDER BY test_runs\.end_time desc`).
108+
WithArgs("abc-123").
109+
WillReturnRows(rows)
110+
111+
w := httptest.NewRecorder()
112+
c, _ := gin.CreateTestContext(w)
113+
114+
c.Request, _ = http.NewRequest("GET", "/testruns?project_uuid=abc-123", nil)
115+
handler := handlers.NewHandler(gormDb)
116+
handler.GetTestRunAll(c)
117+
118+
Expect(w.Code).To(Equal(http.StatusOK))
119+
})
120+
121+
It("should apply valid sort_by and order parameters", func() {
122+
rows := sqlmock.NewRows([]string{
123+
"id", "test_project_name", "project_id", "test_seed", "start_time", "end_time", "git_branch", "git_sha", "build_trigger_actor", "build_url", "status",
124+
}).AddRow(1, "project 1", 101, 0, time.Now(), time.Now(), "main", "abc123", "ci", "url", "PASSED")
125+
126+
mock.ExpectQuery(`(?i)SELECT .* FROM "test_runs" ORDER BY test_runs\.start_time asc`).
127+
WillReturnRows(rows)
128+
129+
w := httptest.NewRecorder()
130+
c, _ := gin.CreateTestContext(w)
131+
132+
c.Request, _ = http.NewRequest("GET", "/testruns?sort_by=start_time&order=asc", nil)
133+
handler := handlers.NewHandler(gormDb)
134+
handler.GetTestRunAll(c)
135+
136+
Expect(w.Code).To(Equal(http.StatusOK))
137+
})
138+
139+
It("should preload fields when specified", func() {
140+
testRunRows := sqlmock.NewRows([]string{
141+
"id", "test_project_name", "project_id", "test_seed", "start_time", "end_time", "git_branch", "git_sha", "build_trigger_actor", "build_url", "status",
142+
}).AddRow(1, "project 1", 101, 123, time.Now(), time.Now(), "main", "abc123", "dev", "url", "PASSED")
143+
144+
mock.ExpectQuery(regexp.QuoteMeta(`
145+
SELECT * FROM "test_runs"
146+
ORDER BY test_runs.end_time desc`)).
147+
WillReturnRows(testRunRows)
148+
149+
projectRows := sqlmock.NewRows([]string{"id", "uuid", "name", "team_name", "comment", "created_at", "updated_at"}).
150+
AddRow(101, "uuid-123", "project-name", "team-a", "", time.Now(), time.Now())
151+
152+
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "project_details" WHERE "project_details"."id" = $1`)).
153+
WithArgs(101).
154+
WillReturnRows(projectRows)
155+
156+
suiteRunRows := sqlmock.NewRows([]string{"id", "test_run_id", "suite_name", "start_time", "end_time"}).
157+
AddRow(201, 1, "suite-1", time.Now(), time.Now())
158+
159+
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "suite_runs" WHERE "suite_runs"."test_run_id" = $1`)).
160+
WithArgs(1).
161+
WillReturnRows(suiteRunRows)
162+
163+
specRunRows := sqlmock.NewRows([]string{"id", "suite_id", "spec_description", "status", "message", "start_time", "end_time"}).
164+
AddRow(301, 201, "spec-1", "PASSED", "", time.Now(), time.Now())
165+
166+
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "spec_runs" WHERE "spec_runs"."suite_id" = $1`)).
167+
WithArgs(201).
168+
WillReturnRows(specRunRows)
169+
170+
//tagJoinRows := sqlmock.NewRows([]string{"spec_run_id", "tag_id"}).
171+
// AddRow(301, 401)
172+
//
173+
//mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "spec_run_tags" WHERE "spec_run_tags"."spec_run_id" = $1`)).
174+
// WithArgs(301).
175+
// WillReturnRows(tagJoinRows)
176+
//
177+
//tagRows := sqlmock.NewRows([]string{"id", "name"}).
178+
// AddRow(401, "tag-1")
179+
180+
//mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "tags" WHERE "tags"."id" = $1`)).
181+
// WithArgs(401).
182+
// WillReturnRows(tagRows)
183+
184+
w := httptest.NewRecorder()
185+
c, _ := gin.CreateTestContext(w)
186+
c.Request, _ = http.NewRequest("GET", "/testruns?fields=project,suiteruns", nil)
187+
188+
handler := handlers.NewHandler(gormDb)
189+
handler.GetTestRunAll(c)
190+
191+
Expect(w.Code).To(Equal(http.StatusOK))
192+
var testRuns []models.TestRun
193+
Expect(json.NewDecoder(w.Body).Decode(&testRuns)).To(Succeed())
194+
Expect(len(testRuns)).To(Equal(1))
195+
Expect(testRuns[0].Project.Name).To(Equal("project-name"))
196+
Expect(testRuns[0].SuiteRuns[0].SuiteName).To(Equal("suite-1"))
197+
//Expect(testRuns[0].SuiteRuns[0].SpecRuns[0].SpecDescription).To(Equal("spec-1"))
198+
})
77199
})
78200

79201
Context("When GetTestRunByID handler is invoked", func() {
@@ -125,6 +247,7 @@ var _ = Describe("Handlers", func() {
125247
BuildTriggerActor: "Actor Name",
126248
BuildUrl: "https://someurl.com",
127249
TestSeed: 0,
250+
Status: "PASSED",
128251
SuiteRuns: []models.SuiteRun{
129252
{
130253
ID: 1,
@@ -161,8 +284,8 @@ var _ = Describe("Handlers", func() {
161284
WillReturnRows(rows)
162285

163286
mock.ExpectBegin()
164-
mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "test_runs" ("test_project_name","project_id","test_seed","start_time","end_time","git_branch","git_sha","build_trigger_actor","build_url") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING "id"`)).
165-
WithArgs(expectedTestRun.TestProjectName, expectedProject.ID, expectedTestRun.TestSeed, expectedTestRun.StartTime, expectedTestRun.EndTime, expectedTestRun.GitBranch, expectedTestRun.GitSha, expectedTestRun.BuildTriggerActor, expectedTestRun.BuildUrl).
287+
mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "test_runs" ("test_project_name","project_id","test_seed","start_time","end_time","git_branch","git_sha","build_trigger_actor","build_url","status") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) RETURNING "id"`)).
288+
WithArgs(expectedTestRun.TestProjectName, expectedProject.ID, expectedTestRun.TestSeed, expectedTestRun.StartTime, expectedTestRun.EndTime, expectedTestRun.GitBranch, expectedTestRun.GitSha, expectedTestRun.BuildTriggerActor, expectedTestRun.BuildUrl, expectedTestRun.Status).
166289
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
167290
mock.ExpectCommit()
168291

@@ -1061,11 +1184,12 @@ var _ = Describe("Handlers", func() {
10611184
AddRow(1, "TestSuite1", "TestProject", time.Date(2024, 4, 20, 12, 0, 0, 0, time.UTC), 5, 1, 10).
10621185
AddRow(2, "TestSuite2", "TestProject", time.Date(2024, 4, 21, 12, 0, 0, 0, time.UTC), 7, 2, 12)
10631186

1187+
//TODO: Update the expected query
10641188
mock.ExpectQuery(regexp.QuoteMeta(`SELECT suite_runs.id AS suite_run_id,
10651189
suite_runs.suite_name,
10661190
test_runs.start_time,
1067-
COUNT(spec_runs.id) FILTER (WHERE spec_runs.status = 'passed') AS total_passed_spec_runs,
1068-
COUNT(spec_runs.id) FILTER (WHERE spec_runs.status = 'skipped') AS total_skipped_spec_runs,
1191+
COUNT(spec_runs.id) FILTER (WHERE LOWER(spec_runs.status) = 'passed') AS total_passed_spec_runs,
1192+
COUNT(spec_runs.id) FILTER (WHERE LOWER(spec_runs.status) = 'skipped') AS total_skipped_spec_runs,
10691193
COUNT(spec_runs.id) AS total_spec_runs FROM "test_runs"
10701194
INNER JOIN suite_runs ON test_runs.id = suite_runs.test_run_id
10711195
INNER JOIN spec_runs ON suite_runs.id = spec_runs.suite_id

0 commit comments

Comments
 (0)