Skip to content

Commit 1042d35

Browse files
committed
Merge remote-tracking branch 'origin/main' into checkpreview
* origin/main: Fix relative-time RangeError (go-gitea#37021) Restyle Workflow Graph (go-gitea#36912) Update message severity colors, fix navbar double border (go-gitea#37019) Clean up checkbox cursor styles (go-gitea#37016) add missing cron tasks to example ini (go-gitea#37012) Add e2e tests for server push events (go-gitea#36879)
2 parents 896a542 + 7492251 commit 1042d35

28 files changed

Lines changed: 761 additions & 648 deletions

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ ifneq ($(and $(STATIC),$(findstring pam,$(TAGS))),)
672672
endif
673673
CGO_ENABLED="$(CGO_ENABLED)" CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@
674674

675-
$(EXECUTABLE_E2E): $(GO_SOURCES)
675+
$(EXECUTABLE_E2E): $(GO_SOURCES) $(WEBPACK_DEST)
676676
CGO_ENABLED=1 $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TEST_TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@
677677

678678
.PHONY: release

custom/conf/app.example.ini

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2276,6 +2276,22 @@ LEVEL = Info
22762276
;; Unreferenced blobs created more than OLDER_THAN ago are subject to deletion
22772277
;OLDER_THAN = 24h
22782278

2279+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2280+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2281+
;; Synchronize repository licenses
2282+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2283+
;[cron.sync_repo_licenses]
2284+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2285+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2286+
;; Whether to enable the job
2287+
;ENABLED = false
2288+
;; Whether to always run at least once at start up time (if ENABLED)
2289+
;RUN_AT_START = false
2290+
;; Whether to emit notice on successful execution too
2291+
;NOTICE_ON_SUCCESS = false
2292+
;; Time interval for job to run
2293+
;SCHEDULE = @annually
2294+
22792295
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
22802296
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
22812297
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -2337,6 +2353,18 @@ LEVEL = Info
23372353
;NOTICE_ON_SUCCESS = false
23382354
;SCHEDULE = @every 72h
23392355

2356+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2357+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2358+
;; Update the '.ssh/authorized_principals' file
2359+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2360+
;[cron.resync_all_sshprincipals]
2361+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2362+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2363+
;ENABLED = false
2364+
;RUN_AT_START = false
2365+
;NOTICE_ON_SUCCESS = false
2366+
;SCHEDULE = @every 72h
2367+
23402368
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
23412369
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
23422370
;; Resynchronize git hooks of all repositories (pre-receive, update, post-receive, proc-receive, ...)
@@ -2445,6 +2473,70 @@ LEVEL = Info
24452473
;Check at least this proportion of LFSMetaObjects per repo. (This may cause all stale LFSMetaObjects to be checked.)
24462474
;PROPORTION_TO_CHECK_PER_REPO = 0.6
24472475

2476+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2477+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2478+
;; Rebuild issue index
2479+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2480+
;[cron.rebuild_issue_indexer]
2481+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2482+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2483+
;ENABLED = false
2484+
;RUN_AT_START = false
2485+
;NO_SUCCESS_NOTICE = false
2486+
;SCHEDULE = @annually
2487+
2488+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2489+
;; Actions cron tasks
2490+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2491+
2492+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2493+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2494+
;; Stop running tasks which haven't been updated for a long time
2495+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2496+
;[cron.stop_zombie_tasks]
2497+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2498+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2499+
;ENABLED = true
2500+
;RUN_AT_START = true
2501+
;NO_SUCCESS_NOTICE = false
2502+
;SCHEDULE = @every 5m
2503+
2504+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2505+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2506+
;; Stop running tasks which have running status and continuous updates but don't end for a long time
2507+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2508+
;[cron.stop_endless_tasks]
2509+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2510+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2511+
;ENABLED = true
2512+
;RUN_AT_START = true
2513+
;NO_SUCCESS_NOTICE = false
2514+
;SCHEDULE = @every 30m
2515+
2516+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2517+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2518+
;; Cancel jobs which haven't been picked up for a long time
2519+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2520+
;[cron.cancel_abandoned_jobs]
2521+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2522+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2523+
;ENABLED = true
2524+
;RUN_AT_START = false
2525+
;NO_SUCCESS_NOTICE = false
2526+
;SCHEDULE = @every 6h
2527+
2528+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2529+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2530+
;; Start cron based actions
2531+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2532+
;[cron.start_schedule_tasks]
2533+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2534+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2535+
;ENABLED = true
2536+
;RUN_AT_START = false
2537+
;NO_SUCCESS_NOTICE = false
2538+
;SCHEDULE = @every 1m
2539+
24482540
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
24492541
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
24502542
;[mirror]

modules/setting/setting.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ var (
2828
CfgProvider ConfigProvider
2929
IsWindows bool
3030

31-
// IsInTesting indicates whether the testing is running. A lot of unreliable code causes a lot of nonsense error logs during testing
32-
// TODO: this is only a temporary solution, we should make the test code more reliable
31+
// IsInTesting indicates whether the testing is running (unit test or integration test). It can be used for:
32+
// * Skip nonsense error logs during testing caused by unreliable code (TODO: this is only a temporary solution, we should make the test code more reliable)
33+
// * Panic in dev or testing mode to make the problem more obvious and easier to debug
34+
// * Mock some functions or options to make testing easier (eg: session store, time, URL detection, etc.)
3335
IsInTesting = false
3436
)
3537

@@ -57,6 +59,10 @@ func IsRunUserMatchCurrentUser(runUser string) (string, bool) {
5759
return currentUser, runUser == currentUser
5860
}
5961

62+
func IsInE2eTesting() bool {
63+
return os.Getenv("GITEA_TEST_E2E") == "true"
64+
}
65+
6066
// PrepareAppDataPath creates app data directory if necessary
6167
func PrepareAppDataPath() error {
6268
// FIXME: There are too many calls to MkdirAll in old code. It is incorrect.

routers/web/devtest/mock_actions.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ func MockActionsRunsJobs(ctx *context.Context) {
6868
runID := ctx.PathParamInt64("run")
6969

7070
resp := &actions.ViewResponse{}
71+
resp.State.Run.RepoID = 12345
7172
resp.State.Run.TitleHTML = `mock run title <a href="/">link</a>`
7273
resp.State.Run.Link = setting.AppSubURL + "/devtest/repo-action-view/runs/" + strconv.FormatInt(runID, 10)
7374
resp.State.Run.Status = actions_model.StatusRunning.String()
@@ -135,12 +136,36 @@ func MockActionsRunsJobs(ctx *context.Context) {
135136
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{
136137
ID: runID*10 + 2,
137138
JobID: "job-102",
138-
Name: "job 102",
139+
Name: "ULTRA LOOOOOOOOOOOONG job name 102 that exceeds the limit",
139140
Status: actions_model.StatusFailure.String(),
140141
CanRerun: false,
141142
Duration: "3h",
142143
Needs: []string{"job-100", "job-101"},
143144
})
145+
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{
146+
ID: runID*10 + 3,
147+
JobID: "job-103",
148+
Name: "job 103",
149+
Status: actions_model.StatusCancelled.String(),
150+
CanRerun: false,
151+
Duration: "2m",
152+
Needs: []string{"job-100"},
153+
})
154+
155+
// add more jobs to a run for UI testing
156+
if resp.State.Run.CanCancel {
157+
for i := range 10 {
158+
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{
159+
ID: runID*1000 + int64(i),
160+
JobID: "job-dup-test-" + strconv.Itoa(i),
161+
Name: "job dup test " + strconv.Itoa(i),
162+
Status: actions_model.StatusSuccess.String(),
163+
CanRerun: false,
164+
Duration: "2m",
165+
Needs: []string{"job-103", "job-101", "job-100"},
166+
})
167+
}
168+
}
144169

145170
fillViewRunResponseCurrentJob(ctx, resp)
146171
ctx.JSON(http.StatusOK, resp)

routers/web/repo/actions/view.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ type ViewResponse struct {
129129

130130
State struct {
131131
Run struct {
132+
RepoID int64 `json:"repoId"`
132133
Link string `json:"link"`
133134
Title string `json:"title"`
134135
TitleHTML template.HTML `json:"titleHTML"`
@@ -252,6 +253,7 @@ func fillViewRunResponseSummary(ctx *context_module.Context, resp *ViewResponse,
252253
return
253254
}
254255

256+
resp.State.Run.RepoID = ctx.Repo.Repository.ID
255257
// the title for the "run" is from the commit message
256258
resp.State.Run.Title = run.Title
257259
resp.State.Run.TitleHTML = templates.NewRenderUtils(ctx).RenderCommitMessage(run.Title, ctx.Repo.Repository)

routers/web/web.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1740,7 +1740,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
17401740
m.Get("/swagger.v1.json", SwaggerV1Json)
17411741
}
17421742

1743-
if !setting.IsProd {
1743+
if !setting.IsProd || setting.IsInE2eTesting() {
17441744
m.Group("/devtest", func() {
17451745
m.Any("", devtest.List)
17461746
m.Any("/fetch-action-test", devtest.FetchActionTest)

templates/devtest/relative-time.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<div class="tw-grid tw-grid-cols-3 tw-gap-4">
44
<div>
55
<h2>Relative (auto)</h2>
6-
<div>now: <relative-time datetime="{{.TimeNow.Format "2006-01-02T15:04:05Z07:00"}}"></relative-time></div>
6+
<div>now: <relative-time data-testid="relative-time-now" datetime="{{.TimeNow.Format "2006-01-02T15:04:05Z07:00"}}"></relative-time></div>
77
<div>3m ago: <relative-time datetime="{{.TimePast3m.Format "2006-01-02T15:04:05Z07:00"}}"></relative-time></div>
88
<div>3h ago: <relative-time datetime="{{.TimePast3h.Format "2006-01-02T15:04:05Z07:00"}}"></relative-time></div>
99
<div>1d ago: <relative-time datetime="{{.TimePast1d.Format "2006-01-02T15:04:05Z07:00"}}"></relative-time></div>

tests/e2e/events.test.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import {test, expect} from '@playwright/test';
2+
import {loginUser, baseUrl, apiUserHeaders, apiCreateUser, apiDeleteUser, apiCreateRepo, apiCreateIssue, apiStartStopwatch} from './utils.ts';
3+
4+
// These tests rely on a short EVENT_SOURCE_UPDATE_TIME in the e2e server config.
5+
test.describe('events', () => {
6+
test('notification count', async ({page, request}) => {
7+
const id = `ev-notif-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
8+
const owner = `${id}-owner`;
9+
const commenter = `${id}-commenter`;
10+
const repoName = id;
11+
12+
await Promise.all([apiCreateUser(request, owner), apiCreateUser(request, commenter)]);
13+
14+
// Create repo and login in parallel — repo is needed for the issue, login for the event stream
15+
await Promise.all([
16+
apiCreateRepo(request, {name: repoName, headers: apiUserHeaders(owner)}),
17+
loginUser(page, owner),
18+
]);
19+
const badge = page.locator('a.not-mobile .notification_count');
20+
await expect(badge).toBeHidden();
21+
22+
// Create issue as another user — this generates a notification delivered via server push
23+
await apiCreateIssue(request, owner, repoName, {title: 'events notification test', headers: apiUserHeaders(commenter)});
24+
25+
// Wait for the notification badge to appear via server event
26+
await expect(badge).toBeVisible({timeout: 15000});
27+
28+
// Cleanup
29+
await Promise.all([apiDeleteUser(request, commenter), apiDeleteUser(request, owner)]);
30+
});
31+
32+
test('stopwatch', async ({page, request}) => {
33+
const name = `ev-sw-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
34+
const headers = apiUserHeaders(name);
35+
36+
await apiCreateUser(request, name);
37+
38+
// Create repo, issue, and start stopwatch before login
39+
await apiCreateRepo(request, {name, headers});
40+
await apiCreateIssue(request, name, name, {title: 'events stopwatch test', headers});
41+
await apiStartStopwatch(request, name, name, 1, {headers});
42+
43+
// Login — page renders with the active stopwatch element
44+
await loginUser(page, name);
45+
46+
// Verify stopwatch is visible and links to the correct issue
47+
const stopwatch = page.locator('.active-stopwatch.not-mobile');
48+
await expect(stopwatch).toBeVisible();
49+
50+
// Cleanup
51+
await apiDeleteUser(request, name);
52+
});
53+
54+
test('logout propagation', async ({browser, request}) => {
55+
const name = `ev-logout-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
56+
57+
await apiCreateUser(request, name);
58+
59+
// Use a single context so both pages share the same session and SharedWorker
60+
const context = await browser.newContext({baseURL: baseUrl()});
61+
const page1 = await context.newPage();
62+
const page2 = await context.newPage();
63+
64+
await loginUser(page1, name);
65+
66+
// Navigate page2 so it connects to the shared event stream
67+
await page2.goto('/');
68+
69+
// Verify page2 is logged in
70+
await expect(page2.getByRole('link', {name: 'Sign In'})).toBeHidden();
71+
72+
// Logout from page1 — this sends a logout event to all tabs
73+
await page1.goto('/user/logout');
74+
75+
// page2 should be redirected via the logout event
76+
await expect(page2.getByRole('link', {name: 'Sign In'})).toBeVisible();
77+
78+
await context.close();
79+
80+
// Cleanup
81+
await apiDeleteUser(request, name);
82+
});
83+
});

tests/e2e/register.test.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {env} from 'node:process';
22
import {test, expect} from '@playwright/test';
3-
import {login, logout} from './utils.ts';
3+
import {login, logout, apiDeleteUser} from './utils.ts';
44

55
test.beforeEach(async ({page}) => {
66
await page.goto('/user/sign_up');
@@ -50,10 +50,7 @@ test('register then login', async ({page}) => {
5050
await login(page, username, password);
5151

5252
// delete via API because of issues related to form-fetch-action
53-
const response = await page.request.delete(`/api/v1/admin/users/${username}?purge=true`, {
54-
headers: {Authorization: `Basic ${btoa(`${env.GITEA_TEST_E2E_USER}:${env.GITEA_TEST_E2E_PASSWORD}`)}`},
55-
});
56-
expect(response.ok()).toBeTruthy();
53+
await apiDeleteUser(page.request, username);
5754
});
5855

5956
test('register with existing username shows error', async ({page}) => {

tests/e2e/relative-time.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {test, expect} from '@playwright/test';
2+
import {assertNoJsError} from './utils.ts';
3+
4+
test('relative-time renders without errors', async ({page}) => {
5+
await page.goto('/devtest/relative-time');
6+
const relativeTime = page.getByTestId('relative-time-now');
7+
await expect(relativeTime).toHaveAttribute('data-tooltip-content', /.+/);
8+
await expect(relativeTime).toHaveText('now');
9+
await assertNoJsError(page);
10+
});

0 commit comments

Comments
 (0)