// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package devtest import ( "fmt" mathRand "math/rand/v2" "net/http" "strings" "time" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/web/repo/actions" "code.gitea.io/gitea/services/context" ) func generateMockStepsLog(logCur actions.LogCursor) (stepsLog []*actions.ViewStepLog) { mockedLogs := []string{ "::group::test group for: step={step}, cursor={cursor}", "in group msg for: step={step}, cursor={cursor}", "in group msg for: step={step}, cursor={cursor}", "in group msg for: step={step}, cursor={cursor}", "::endgroup::", "message for: step={step}, cursor={cursor}", "message for: step={step}, cursor={cursor}", "message for: step={step}, cursor={cursor}", "message for: step={step}, cursor={cursor}", "message for: step={step}, cursor={cursor}", } cur := logCur.Cursor // usually the cursor is the "file offset", but here we abuse it as "line number" to make the mock easier, intentionally for i := 0; i < util.Iif(logCur.Step == 0, 3, 1); i++ { logStr := mockedLogs[int(cur)%len(mockedLogs)] cur++ logStr = strings.ReplaceAll(logStr, "{step}", fmt.Sprintf("%d", logCur.Step)) logStr = strings.ReplaceAll(logStr, "{cursor}", fmt.Sprintf("%d", cur)) stepsLog = append(stepsLog, &actions.ViewStepLog{ Step: logCur.Step, Cursor: cur, Started: time.Now().Unix() - 1, Lines: []*actions.ViewStepLogLine{ {Index: cur, Message: logStr, Timestamp: float64(time.Now().UnixNano()) / float64(time.Second)}, }, }) } return stepsLog } func MockActionsRunsJobs(ctx *context.Context) { req := web.GetForm(ctx).(*actions.ViewRequest) resp := &actions.ViewResponse{} resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{ Name: "artifact-a", Size: 100 * 1024, Status: "expired", }) resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{ Name: "artifact-b", Size: 1024 * 1024, Status: "completed", }) resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{ Summary: "step 0 (mock slow)", Duration: time.Hour.String(), Status: actions_model.StatusRunning.String(), }) resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{ Summary: "step 1 (mock fast)", Duration: time.Hour.String(), Status: actions_model.StatusRunning.String(), }) resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{ Summary: "step 2 (mock error)", Duration: time.Hour.String(), Status: actions_model.StatusRunning.String(), }) if len(req.LogCursors) == 0 { ctx.JSON(http.StatusOK, resp) return } resp.Logs.StepsLog = []*actions.ViewStepLog{} doSlowResponse := false doErrorResponse := false for _, logCur := range req.LogCursors { if !logCur.Expanded { continue } doSlowResponse = doSlowResponse || logCur.Step == 0 doErrorResponse = doErrorResponse || logCur.Step == 2 resp.Logs.StepsLog = append(resp.Logs.StepsLog, generateMockStepsLog(logCur)...) } if doErrorResponse { if mathRand.Float64() > 0.5 { ctx.Error(http.StatusInternalServerError, "devtest mock error response") return } } if doSlowResponse { time.Sleep(time.Duration(3000) * time.Millisecond) } else { time.Sleep(time.Duration(100) * time.Millisecond) // actually, frontend reload every 1 second, any smaller delay is fine } ctx.JSON(http.StatusOK, resp) }