// Copyright 2026 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: GPL-3.0-or-later package integration import ( "fmt" "net/http" "testing" repo_model "forgejo.org/models/repo" secret_model "forgejo.org/models/secret" "forgejo.org/models/unittest" user_model "forgejo.org/models/user" app_context "forgejo.org/services/context" "forgejo.org/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestActionsSecretsCreateSecret(t *testing.T) { defer tests.PrepareTestEnv(t)() user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1, OwnerID: user2.ID}) org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization}) sess := loginUser(t, user2.Name) testCases := []struct { name string url string ownerID int64 repoID int64 }{ { name: "User", url: "/user/settings/actions/secrets", ownerID: user2.ID, repoID: 0, }, { name: "Repository", url: "/" + repo1.FullName() + "/settings/actions/secrets", ownerID: 0, repoID: repo1.ID, }, { name: "Organization", url: "/org/" + org3.Name + "/settings/actions/secrets", ownerID: org3.ID, repoID: 0, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { req := NewRequestWithValues(t, "POST", testCase.url, map[string]string{ "name": "my_secret", "data": " \r\n\tSecrët dåtä\\ \r\n", }) sess.MakeRequest(t, req, http.StatusOK) flashCookie := sess.GetCookie(app_context.CookieNameFlash) assert.NotNil(t, flashCookie) assert.Equal(t, "success%3DThe%2Bsecret%2B%2522MY_SECRET%2522%2Bhas%2Bbeen%2Badded.", flashCookie.Value) secret := unittest.AssertExistsAndLoadBean(t, &secret_model.Secret{OwnerID: testCase.ownerID, RepoID: testCase.repoID, Name: "MY_SECRET"}) assert.Equal(t, "MY_SECRET", secret.Name) value, err := secret.GetDecryptedData() require.NoError(t, err) assert.Equal(t, " \n\tSecrët dåtä\\ \n", value) }) } } func TestActionsSecretsCreateSecretRejectsNameMatchingExistingSecret(t *testing.T) { defer tests.PrepareTestEnv(t)() user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1, OwnerID: user2.ID}) org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization}) sess := loginUser(t, user2.Name) testCases := []struct { name string url string ownerID int64 repoID int64 }{ { name: "User", url: "/user/settings/actions/secrets", ownerID: user2.ID, repoID: 0, }, { name: "Repository", url: "/" + repo1.FullName() + "/settings/actions/secrets", ownerID: 0, repoID: repo1.ID, }, { name: "Organization", url: "/org/" + org3.Name + "/settings/actions/secrets", ownerID: org3.ID, repoID: 0, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { req := NewRequestWithValues(t, "POST", testCase.url, map[string]string{ "name": "my_secret", "data": "original value", }) sess.MakeRequest(t, req, http.StatusOK) flashCookie := sess.GetCookie(app_context.CookieNameFlash) assert.NotNil(t, flashCookie) assert.Equal(t, "success%3DThe%2Bsecret%2B%2522MY_SECRET%2522%2Bhas%2Bbeen%2Badded.", flashCookie.Value) secret := unittest.AssertExistsAndLoadBean(t, &secret_model.Secret{OwnerID: testCase.ownerID, RepoID: testCase.repoID, Name: "MY_SECRET"}) assert.Equal(t, "MY_SECRET", secret.Name) value, err := secret.GetDecryptedData() require.NoError(t, err) assert.Equal(t, "original value", value) // Try to create a new secret with the name but another value. req = NewRequestWithValues(t, "POST", testCase.url, map[string]string{ "name": "my_secret", "data": "changed value", }) sess.MakeRequest(t, req, http.StatusBadRequest) // Verify that the original secret has not been changed. secret = unittest.AssertExistsAndLoadBean(t, &secret_model.Secret{OwnerID: testCase.ownerID, RepoID: testCase.repoID, Name: "MY_SECRET"}) value, err = secret.GetDecryptedData() require.NoError(t, err) assert.Equal(t, "MY_SECRET", secret.Name) assert.Equal(t, "original value", value) }) } } func TestActionsSecretsEditSecret(t *testing.T) { defer tests.PrepareTestEnv(t)() user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1, OwnerID: user2.ID}) org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization}) sess := loginUser(t, user2.Name) testCases := []struct { name string url string ownerID int64 repoID int64 }{ { name: "User", url: "/user/settings/actions/secrets", ownerID: user2.ID, repoID: 0, }, { name: "Repository", url: "/" + repo1.FullName() + "/settings/actions/secrets", ownerID: 0, repoID: repo1.ID, }, { name: "Organization", url: "/org/" + org3.Name + "/settings/actions/secrets", ownerID: org3.ID, repoID: 0, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { req := NewRequestWithValues(t, "POST", testCase.url, map[string]string{ "name": "TEST_SECRET", "data": "value", }) sess.MakeRequest(t, req, http.StatusOK) flashCookie := sess.GetCookie(app_context.CookieNameFlash) assert.NotNil(t, flashCookie) assert.Equal(t, "success%3DThe%2Bsecret%2B%2522TEST_SECRET%2522%2Bhas%2Bbeen%2Badded.", flashCookie.Value) secret := unittest.AssertExistsAndLoadBean(t, &secret_model.Secret{OwnerID: testCase.ownerID, RepoID: testCase.repoID, Name: "TEST_SECRET"}) req = NewRequestWithValues(t, "POST", fmt.Sprintf("%s/%d/edit", testCase.url, secret.ID), map[string]string{ "name": secret.Name, "data": " \r\n\tSecrët dåtä\\ \r\n", }) sess.MakeRequest(t, req, http.StatusOK) flashCookie = sess.GetCookie(app_context.CookieNameFlash) assert.NotNil(t, flashCookie) assert.Equal(t, "success%3DThe%2Bsecret%2B%2522TEST_SECRET%2522%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) updatedSecret := unittest.AssertExistsAndLoadBean(t, &secret_model.Secret{ID: secret.ID}) decryptedValue, err := updatedSecret.GetDecryptedData() require.NoError(t, err) assert.Equal(t, secret.Name, updatedSecret.Name) assert.Equal(t, " \n\tSecrët dåtä\\ \n", decryptedValue) }) } } func TestActionsSecretsEditSecretWithoutChangingItsValue(t *testing.T) { defer tests.PrepareTestEnv(t)() user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1, OwnerID: user2.ID}) org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization}) sess := loginUser(t, user2.Name) testCases := []struct { name string url string ownerID int64 repoID int64 }{ { name: "User", url: "/user/settings/actions/secrets", ownerID: user2.ID, repoID: 0, }, { name: "Repository", url: "/" + repo1.FullName() + "/settings/actions/secrets", ownerID: 0, repoID: repo1.ID, }, { name: "Organization", url: "/org/" + org3.Name + "/settings/actions/secrets", ownerID: org3.ID, repoID: 0, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { req := NewRequestWithValues(t, "POST", testCase.url, map[string]string{ "name": "TEST_SECRET", "data": " \r\n\tSecrët dåtä\\ \r\n", }) sess.MakeRequest(t, req, http.StatusOK) flashCookie := sess.GetCookie(app_context.CookieNameFlash) assert.NotNil(t, flashCookie) assert.Equal(t, "success%3DThe%2Bsecret%2B%2522TEST_SECRET%2522%2Bhas%2Bbeen%2Badded.", flashCookie.Value) secret := unittest.AssertExistsAndLoadBean(t, &secret_model.Secret{OwnerID: testCase.ownerID, RepoID: testCase.repoID, Name: "TEST_SECRET"}) req = NewRequestWithValues(t, "POST", fmt.Sprintf("%s/%d/edit", testCase.url, secret.ID), map[string]string{ "name": "changed_secret", "data": "", }) sess.MakeRequest(t, req, http.StatusOK) flashCookie = sess.GetCookie(app_context.CookieNameFlash) assert.NotNil(t, flashCookie) assert.Equal(t, "success%3DThe%2Bsecret%2B%2522CHANGED_SECRET%2522%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) updatedSecret := unittest.AssertExistsAndLoadBean(t, &secret_model.Secret{ID: secret.ID}) decryptedValue, err := updatedSecret.GetDecryptedData() require.NoError(t, err) assert.Equal(t, "CHANGED_SECRET", updatedSecret.Name) assert.Equal(t, " \n\tSecrët dåtä\\ \n", decryptedValue) }) } } func TestActionsSecretsEditSecretRejectsInvalidName(t *testing.T) { defer tests.PrepareTestEnv(t)() user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1, OwnerID: user2.ID}) org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization}) sess := loginUser(t, user2.Name) testCases := []struct { name string url string ownerID int64 repoID int64 }{ { name: "User", url: "/user/settings/actions/secrets", ownerID: user2.ID, repoID: 0, }, { name: "Repository", url: "/" + repo1.FullName() + "/settings/actions/secrets", ownerID: 0, repoID: repo1.ID, }, { name: "Organization", url: "/org/" + org3.Name + "/settings/actions/secrets", ownerID: org3.ID, repoID: 0, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { req := NewRequestWithValues(t, "POST", testCase.url, map[string]string{ "name": "TEST_SECRET", "data": "value", }) sess.MakeRequest(t, req, http.StatusOK) flashCookie := sess.GetCookie(app_context.CookieNameFlash) assert.NotNil(t, flashCookie) assert.Equal(t, "success%3DThe%2Bsecret%2B%2522TEST_SECRET%2522%2Bhas%2Bbeen%2Badded.", flashCookie.Value) secret := unittest.AssertExistsAndLoadBean(t, &secret_model.Secret{OwnerID: testCase.ownerID, RepoID: testCase.repoID, Name: "TEST_SECRET"}) req = NewRequestWithValues(t, "POST", fmt.Sprintf("%s/%d/edit", testCase.url, secret.ID), map[string]string{ "name": "FORGEJO_IS_INVALID", "data": "", }) sess.MakeRequest(t, req, http.StatusBadRequest) }) } } func TestActionsSecretsRemoveSecret(t *testing.T) { defer tests.PrepareTestEnv(t)() user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1, OwnerID: user2.ID}) org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization}) sess := loginUser(t, user2.Name) testCases := []struct { name string url string ownerID int64 repoID int64 }{ { name: "User", url: "/user/settings/actions/secrets", ownerID: user2.ID, repoID: 0, }, { name: "Repository", url: "/" + repo1.FullName() + "/settings/actions/secrets", ownerID: 0, repoID: repo1.ID, }, { name: "Organization", url: "/org/" + org3.Name + "/settings/actions/secrets", ownerID: org3.ID, repoID: 0, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { req := NewRequestWithValues(t, "POST", testCase.url, map[string]string{ "name": "TEST_SECRET", "data": "value", }) sess.MakeRequest(t, req, http.StatusOK) flashCookie := sess.GetCookie(app_context.CookieNameFlash) assert.NotNil(t, flashCookie) assert.Equal(t, "success%3DThe%2Bsecret%2B%2522TEST_SECRET%2522%2Bhas%2Bbeen%2Badded.", flashCookie.Value) secret := unittest.AssertExistsAndLoadBean(t, &secret_model.Secret{OwnerID: testCase.ownerID, RepoID: testCase.repoID, Name: "TEST_SECRET"}) req = NewRequest(t, "POST", fmt.Sprintf("%s/%d/delete", testCase.url, secret.ID)) sess.MakeRequest(t, req, http.StatusOK) flashCookie = sess.GetCookie(app_context.CookieNameFlash) assert.NotNil(t, flashCookie) assert.Equal(t, "success%3DThe%2Bsecret%2Bhas%2Bbeen%2Bremoved.", flashCookie.Value) unittest.AssertNotExistsBean(t, secret) }) } }