diff options
author | Kyle D <kdumontnu@gmail.com> | 2022-09-02 15:18:23 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-02 15:18:23 -0400 |
commit | c8ded77680db7344c8dc1ccee76bce0b4e02e103 (patch) | |
tree | bc63678ef62dc71ce68b29eeaf019c45cdb12034 /tests | |
parent | 5710ff343c9f16119ddbff06044e5d61388baa22 (diff) | |
download | gitea-c8ded77680db7344c8dc1ccee76bce0b4e02e103.tar.gz gitea-c8ded77680db7344c8dc1ccee76bce0b4e02e103.zip |
Kd/ci playwright go test (#20123)
* Add initial playwright config
* Simplify Makefile
* Simplify Makefile
* Use correct config files
* Update playwright settings
* Fix package-lock file
* Don't use test logger for e2e tests
* fix frontend lint
* Allow passing TEST_LOGGER variable
* Init postgres database
* use standard gitea env variables
* Update playwright
* update drone
* Move empty env var to commands
* Cleanup
* Move integrations to subfolder
* tests integrations to tests integraton
* Run e2e tests with go test
* Fix linting
* install CI deps
* Add files to ESlint
* Fix drone typo
* Don't log to console in CI
* Use go test http server
* Add build step before tests
* Move shared init function to common package
* fix drone
* Clean up tests
* Fix linting
* Better mocking for page + version string
* Cleanup test generation
* Remove dependency on gitea binary
* Fix linting
* add initial support for running specific tests
* Add ACCEPT_VISUAL variable
* don't require git-lfs
* Add initial documentation
* Review feedback
* Add logged in session test
* Attempt fixing drone race
* Cleanup and bump version
* Bump deps
* Review feedback
* simplify installation
* Fix ci
* Update install docs
Diffstat (limited to 'tests')
628 files changed, 26871 insertions, 0 deletions
diff --git a/tests/e2e/README.md b/tests/e2e/README.md new file mode 100644 index 0000000000..c84d7807fc --- /dev/null +++ b/tests/e2e/README.md @@ -0,0 +1,93 @@ +# End to end tests + +E2e tests largely follow the same syntax as [integration tests](tests/e2e/README.md). +Whereas integration tests are intended to mock and stress the back-end, server-side code, e2e tests the interface between front-end and back-end, as well as visual regressions with both assertions and visual comparisons. +They can be run with make commands for the appropriate backends, namely: +```shell +make test-sqlite +make test-pgsql +make test-mysql +make test-mysql8 +make test-mssql +``` + +Make sure to perform a clean front-end build before running tests: +``` +make clean frontend +``` + +## Install playwright system dependencies +``` +npx playwright install-deps +``` + + +## Run all tests via local drone +``` +drone exec --local --build-event "pull_request" +``` + +## Run sqlite e2e tests +Start tests +``` +make test-e2e-sqlite +``` + +## Run MySQL e2e tests +Setup a MySQL database inside docker +``` +docker run -e "MYSQL_DATABASE=test" -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" -p 3306:3306 --rm --name mysql mysql:latest #(just ctrl-c to stop db and clean the container) +docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" --rm --name elasticsearch elasticsearch:7.6.0 #(in a second terminal, just ctrl-c to stop db and clean the container) +``` +Start tests based on the database container +``` +TEST_MYSQL_HOST=localhost:3306 TEST_MYSQL_DBNAME=test TEST_MYSQL_USERNAME=root TEST_MYSQL_PASSWORD='' make test-e2e-mysql +``` + +## Run pgsql e2e tests +Setup a pgsql database inside docker +``` +docker run -e "POSTGRES_DB=test" -p 5432:5432 --rm --name pgsql postgres:latest #(just ctrl-c to stop db and clean the container) +``` +Start tests based on the database container +``` +TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=test TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-e2e-pgsql +``` + +## Run mssql e2e tests +Setup a mssql database inside docker +``` +docker run -e "ACCEPT_EULA=Y" -e "MSSQL_PID=Standard" -e "SA_PASSWORD=MwantsaSecurePassword1" -p 1433:1433 --rm --name mssql microsoft/mssql-server-linux:latest #(just ctrl-c to stop db and clean the container) +``` +Start tests based on the database container +``` +TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=gitea_test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-e2e-mssql +``` + +## Running individual tests + +Example command to run `example.test.e2e.js` test file: + +_Note: unlike integration tests, this filtering is at the file level, not function_ + +For SQLite: + +``` +make test-e2e-sqlite#example +``` + +For other databases(replace `mssql` to `mysql`, `mysql8` or `pgsql`): + +``` +TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-e2e-mssql#example +``` + +## Visual testing + +Although the main goal of e2e is assertion testing, we have added a framework for visual regress testing. If you are working on front-end features, please use the following: + - Check out `main`, `make clean frontend`, and run e2e tests with `VISUAL_TEST=1` to generate outputs. This will initially fail, as no screenshots exist. You can run the e2e tests again to assert it passes. + - Check out your branch, `make clean frontend`, and run e2e tests with `VISUAL_TEST=1`. You should be able to assert you front-end changes don't break any other tests unintentionally. + +VISUAL_TEST=1 will create screenshots in tests/e2e/test-snapshots. The test will fail the first time this is enabled (until we get visual test image persistence figured out), because it will be testing against an empty screenshot folder. + +ACCEPT_VISUAL=1 will overwrite the snapshot images with new images.
\ No newline at end of file diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go new file mode 100644 index 0000000000..c77c071181 --- /dev/null +++ b/tests/e2e/e2e_test.go @@ -0,0 +1,120 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +// This is primarily coped from /tests/integration/integration_test.go +// TODO: Move common functions to shared file + +package e2e + +import ( + "bytes" + "context" + "fmt" + "net/url" + "os" + "os/exec" + "path/filepath" + "testing" + + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers" + "code.gitea.io/gitea/tests" +) + +var c *web.Route + +func TestMain(m *testing.M) { + defer log.Close() + + managerCtx, cancel := context.WithCancel(context.Background()) + graceful.InitManager(managerCtx) + defer cancel() + + tests.InitTest(false) + c = routers.NormalRoutes(context.TODO()) + + os.Unsetenv("GIT_AUTHOR_NAME") + os.Unsetenv("GIT_AUTHOR_EMAIL") + os.Unsetenv("GIT_AUTHOR_DATE") + os.Unsetenv("GIT_COMMITTER_NAME") + os.Unsetenv("GIT_COMMITTER_EMAIL") + os.Unsetenv("GIT_COMMITTER_DATE") + + err := unittest.InitFixtures( + unittest.FixturesOptions{ + Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"), + }, + ) + if err != nil { + fmt.Printf("Error initializing test database: %v\n", err) + os.Exit(1) + } + + exitVal := m.Run() + + tests.WriterCloser.Reset() + + if err = util.RemoveAll(setting.Indexer.IssuePath); err != nil { + fmt.Printf("util.RemoveAll: %v\n", err) + os.Exit(1) + } + if err = util.RemoveAll(setting.Indexer.RepoPath); err != nil { + fmt.Printf("Unable to remove repo indexer: %v\n", err) + os.Exit(1) + } + + os.Exit(exitVal) +} + +// This should be the only test e2e necessary. It will collect all "*.test.e2e.js" +// files in this directory and build a test for each. +func TestE2e(t *testing.T) { + // Find the paths of all e2e test files in test test directory. + searchGlob := filepath.Join(filepath.Dir(setting.AppPath), "tests", "e2e", "*.test.e2e.js") + paths, err := filepath.Glob(searchGlob) + if err != nil { + t.Fatal(err) + } else if len(paths) == 0 { + t.Fatal(fmt.Errorf("No e2e tests found in %s", searchGlob)) + } + + runArgs := []string{"npx", "playwright", "test"} + + // To update snapshot outputs + if _, set := os.LookupEnv("ACCEPT_VISUAL"); set { + runArgs = append(runArgs, "--update-snapshots") + } + + // Create new test for each input file + for _, path := range paths { + _, filename := filepath.Split(path) + testname := filename[:len(filename)-len(filepath.Ext(path))] + + t.Run(testname, func(t *testing.T) { + // Default 2 minute timeout + onGiteaRun(t, func(*testing.T, *url.URL) { + cmd := exec.Command(runArgs[0], runArgs...) + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, fmt.Sprintf("GITEA_URL=%s", setting.AppURL)) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + // Currently colored output is conflicting. Using Printf until that is resolved. + fmt.Printf("%v", stdout.String()) + fmt.Printf("%v", stderr.String()) + log.Fatal("Playwright Failed: %s", err) + } else { + fmt.Printf("%v", stdout.String()) + } + }) + }) + } +} diff --git a/tests/e2e/example.test.e2e.js b/tests/e2e/example.test.e2e.js new file mode 100644 index 0000000000..bd19ceb8fc --- /dev/null +++ b/tests/e2e/example.test.e2e.js @@ -0,0 +1,57 @@ +// @ts-check +import {test, expect} from '@playwright/test'; +import {login_user, save_visual, load_logged_in_context} from './utils_e2e.js'; + +test.beforeAll(async ({browser}, workerInfo) => { + await login_user(browser, workerInfo, 'user2'); +}); + +test('Load Homepage', async ({page}) => { + const response = await page.goto('/'); + await expect(response?.status()).toBe(200); // Status OK + await expect(page).toHaveTitle(/^Gitea: Git with a cup of tea\s*$/); + await expect(page.locator('.logo')).toHaveAttribute('src', '/assets/img/logo.svg'); +}); + +test('Test Register Form', async ({page}, workerInfo) => { + const response = await page.goto('/user/sign_up'); + await expect(response?.status()).toBe(200); // Status OK + await page.type('input[name=user_name]', `e2e-test-${workerInfo.workerIndex}`); + await page.type('input[name=email]', `e2e-test-${workerInfo.workerIndex}@test.com`); + await page.type('input[name=password]', 'test123'); + await page.type('input[name=retype]', 'test123'); + await page.click('form button.ui.green.button:visible'); + // Make sure we routed to the home page. Else login failed. + await expect(page.url()).toBe(`${workerInfo.project.use.baseURL}/`); + await expect(page.locator('.dashboard-navbar span>img.ui.avatar.image')).toBeVisible(); + await expect(page.locator('.ui.positive.message.flash-success')).toHaveText('Account was successfully created.'); + + save_visual(page); +}); + +test('Test Login Form', async ({page}, workerInfo) => { + const response = await page.goto('/user/login'); + await expect(response?.status()).toBe(200); // Status OK + + await page.type('input[name=user_name]', `user2`); + await page.type('input[name=password]', `password`); + await page.click('form button.ui.green.button:visible'); + + await page.waitForLoadState('networkidle'); + + await expect(page.url()).toBe(`${workerInfo.project.use.baseURL}/`); + + save_visual(page); +}); + +test('Test Logged In User', async ({browser}, workerInfo) => { + const context = await load_logged_in_context(browser, workerInfo, 'user2'); + const page = await context.newPage(); + + await page.goto('/'); + + // Make sure we routed to the home page. Else login failed. + await expect(page.url()).toBe(`${workerInfo.project.use.baseURL}/`); + + save_visual(page); +}); diff --git a/tests/e2e/utils_e2e.js b/tests/e2e/utils_e2e.js new file mode 100644 index 0000000000..b5b9ce2751 --- /dev/null +++ b/tests/e2e/utils_e2e.js @@ -0,0 +1,60 @@ +import {expect} from '@playwright/test'; + +const ARTIFACTS_PATH = `tests/e2e/test-artifacts`; +const LOGIN_PASSWORD = 'password'; + +// log in user and store session info. This should generally be +// run in test.beforeAll(), then the session can be loaded in tests. +export async function login_user(browser, workerInfo, user) { + // Set up a new context + const context = await browser.newContext(); + const page = await context.newPage(); + + // Route to login page + // Note: this could probably be done more quickly with a POST + const response = await page.goto('/user/login'); + await expect(response?.status()).toBe(200); // Status OK + + // Fill out form + await page.type('input[name=user_name]', user); + await page.type('input[name=password]', LOGIN_PASSWORD); + await page.click('form button.ui.green.button:visible'); + + await page.waitForLoadState('networkidle'); + + await expect(page.url(), {message: `Failed to login user ${user}`}).toBe(`${workerInfo.project.use.baseURL}/`); + + // Save state + await context.storageState({path: `${ARTIFACTS_PATH}/state-${user}-${workerInfo.workerIndex}.json`}); + + return context; +} + +export async function load_logged_in_context(browser, workerInfo, user) { + let context; + try { + context = await browser.newContext({storageState: `${ARTIFACTS_PATH}/state-${user}-${workerInfo.workerIndex}.json`}); + } catch (err) { + if (err.code === 'ENOENT') { + throw new Error(`Could not find state for '${user}'. Did you call login_user(browser, workerInfo, '${user}') in test.beforeAll()?`); + } + } + return context; +} + +export async function save_visual(page) { + // Optionally include visual testing + if (process.env.VISUAL_TEST) { + await page.waitForLoadState('networkidle'); + // Mock page/version string + await page.locator('footer div.ui.left').evaluate((node) => node.innerHTML = 'MOCK'); + await expect(page).toHaveScreenshot({ + fullPage: true, + timeout: 20000, + mask: [ + page.locator('.dashboard-navbar span>img.ui.avatar.image'), + page.locator('.ui.dropdown.jump.item.tooltip span>img.ui.avatar.image'), + ], + }); + } +} diff --git a/tests/e2e/utils_e2e_test.go b/tests/e2e/utils_e2e_test.go new file mode 100644 index 0000000000..f8f3e94cda --- /dev/null +++ b/tests/e2e/utils_e2e_test.go @@ -0,0 +1,57 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package e2e + +import ( + "context" + "net" + "net/http" + "net/url" + "testing" + "time" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func onGiteaRunTB(t testing.TB, callback func(testing.TB, *url.URL), prepare ...bool) { + if len(prepare) == 0 || prepare[0] { + defer tests.PrepareTestEnv(t, 1)() + } + s := http.Server{ + Handler: c, + } + + u, err := url.Parse(setting.AppURL) + assert.NoError(t, err) + listener, err := net.Listen("tcp", u.Host) + i := 0 + for err != nil && i <= 10 { + time.Sleep(100 * time.Millisecond) + listener, err = net.Listen("tcp", u.Host) + i++ + } + assert.NoError(t, err) + u.Host = listener.Addr().String() + + defer func() { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + s.Shutdown(ctx) + cancel() + }() + + go s.Serve(listener) + // Started by config go ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) + + callback(t, u) +} + +func onGiteaRun(t *testing.T, callback func(*testing.T, *url.URL), prepare ...bool) { + onGiteaRunTB(t, func(t testing.TB, u *url.URL) { + callback(t.(*testing.T), u) + }, prepare...) +} diff --git a/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/HEAD b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/config b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/config new file mode 100644 index 0000000000..e6da231579 --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true diff --git a/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/description b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/info/exclude b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/objects/74/8bf557dfc9c6457998b5118a6c8b2129f56c30 b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/objects/74/8bf557dfc9c6457998b5118a6c8b2129f56c30 Binary files differnew file mode 100644 index 0000000000..980093a921 --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/objects/74/8bf557dfc9c6457998b5118a6c8b2129f56c30 diff --git a/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/objects/a5/46f86c7dd182592b96639045e176dde8df76ef b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/objects/a5/46f86c7dd182592b96639045e176dde8df76ef Binary files differnew file mode 100644 index 0000000000..b8b1449445 --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/objects/a5/46f86c7dd182592b96639045e176dde8df76ef diff --git a/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/objects/b8/95782bd271fdd266dd06e5880ea4abdc3a0dc7 b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/objects/b8/95782bd271fdd266dd06e5880ea4abdc3a0dc7 Binary files differnew file mode 100644 index 0000000000..6b6d55bf98 --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/objects/b8/95782bd271fdd266dd06e5880ea4abdc3a0dc7 diff --git a/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/refs/heads/master b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/refs/heads/master new file mode 100644 index 0000000000..70132ddec1 --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/private_repo_on_limited_org.git/refs/heads/master @@ -0,0 +1 @@ +b895782bd271fdd266dd06e5880ea4abdc3a0dc7 diff --git a/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/HEAD b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/config b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/config new file mode 100644 index 0000000000..e6da231579 --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true diff --git a/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/description b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/info/exclude b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/objects/21/2f14c8b713de38bd0b3fb23bd288369b01668a b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/objects/21/2f14c8b713de38bd0b3fb23bd288369b01668a Binary files differnew file mode 100644 index 0000000000..45a0732001 --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/objects/21/2f14c8b713de38bd0b3fb23bd288369b01668a diff --git a/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/objects/90/e402c3937a4639725fcc59ca1f529e7dc8506f b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/objects/90/e402c3937a4639725fcc59ca1f529e7dc8506f Binary files differnew file mode 100644 index 0000000000..b73b5a5c0f --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/objects/90/e402c3937a4639725fcc59ca1f529e7dc8506f diff --git a/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/objects/ed/d9c1000cd1444efd63e153e3554c8d5656bf65 b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/objects/ed/d9c1000cd1444efd63e153e3554c8d5656bf65 Binary files differnew file mode 100644 index 0000000000..f69006315e --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/objects/ed/d9c1000cd1444efd63e153e3554c8d5656bf65 diff --git a/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/refs/heads/master b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/refs/heads/master new file mode 100644 index 0000000000..9de5b8d826 --- /dev/null +++ b/tests/gitea-repositories-meta/limited_org/public_repo_on_limited_org.git/refs/heads/master @@ -0,0 +1 @@ +90e402c3937a4639725fcc59ca1f529e7dc8506f diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/HEAD b/tests/gitea-repositories-meta/migration/lfs-test.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/config b/tests/gitea-repositories-meta/migration/lfs-test.git/config new file mode 100644 index 0000000000..3f8f41b6b4 --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/config @@ -0,0 +1,7 @@ +[core] + bare = false + repositoryformatversion = 0 + filemode = false + symlinks = false + ignorecase = true + logallrefupdates = true diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/description b/tests/gitea-repositories-meta/migration/lfs-test.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/hooks/post-checkout b/tests/gitea-repositories-meta/migration/lfs-test.git/hooks/post-checkout new file mode 100644 index 0000000000..cab40f2649 --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/hooks/post-checkout @@ -0,0 +1,3 @@ +#!/bin/sh +command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/post-checkout.\n"; exit 2; } +git lfs post-checkout "$@" diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/hooks/post-commit b/tests/gitea-repositories-meta/migration/lfs-test.git/hooks/post-commit new file mode 100644 index 0000000000..9443f4161a --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/hooks/post-commit @@ -0,0 +1,3 @@ +#!/bin/sh +command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/post-commit.\n"; exit 2; } +git lfs post-commit "$@" diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/hooks/post-merge b/tests/gitea-repositories-meta/migration/lfs-test.git/hooks/post-merge new file mode 100644 index 0000000000..828b70891e --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/hooks/post-merge @@ -0,0 +1,3 @@ +#!/bin/sh +command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/post-merge.\n"; exit 2; } +git lfs post-merge "$@" diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/hooks/pre-push b/tests/gitea-repositories-meta/migration/lfs-test.git/hooks/pre-push new file mode 100644 index 0000000000..81a9cc6398 --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/hooks/pre-push @@ -0,0 +1,3 @@ +#!/bin/sh +command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/pre-push.\n"; exit 2; } +git lfs pre-push "$@" diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/index b/tests/gitea-repositories-meta/migration/lfs-test.git/index Binary files differnew file mode 100644 index 0000000000..13f8e26966 --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/index diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/lfs/objects/d6/f1/d6f175817f886ec6fbbc1515326465fa96c3bfd54a4ea06cfd6dbbd8340e0152 b/tests/gitea-repositories-meta/migration/lfs-test.git/lfs/objects/d6/f1/d6f175817f886ec6fbbc1515326465fa96c3bfd54a4ea06cfd6dbbd8340e0152 new file mode 100644 index 0000000000..e9b0a4e8a0 --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/lfs/objects/d6/f1/d6f175817f886ec6fbbc1515326465fa96c3bfd54a4ea06cfd6dbbd8340e0152 @@ -0,0 +1 @@ +dummy2
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/lfs/objects/fb/8f/fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041 b/tests/gitea-repositories-meta/migration/lfs-test.git/lfs/objects/fb/8f/fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041 new file mode 100644 index 0000000000..71676cd9c0 --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/lfs/objects/fb/8f/fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041 @@ -0,0 +1 @@ +dummy1
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/objects/54/6244003622c64b2fc3c2cd544d7a29882c8383 b/tests/gitea-repositories-meta/migration/lfs-test.git/objects/54/6244003622c64b2fc3c2cd544d7a29882c8383 Binary files differnew file mode 100644 index 0000000000..0db52afbca --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/objects/54/6244003622c64b2fc3c2cd544d7a29882c8383 diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/objects/6a/6ccf5d874fec134ee712572cc03a0f2dd7afec b/tests/gitea-repositories-meta/migration/lfs-test.git/objects/6a/6ccf5d874fec134ee712572cc03a0f2dd7afec Binary files differnew file mode 100644 index 0000000000..8a96927e57 --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/objects/6a/6ccf5d874fec134ee712572cc03a0f2dd7afec diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/objects/a6/7134b8484c2abe9fa954e1fd83b39b271383ed b/tests/gitea-repositories-meta/migration/lfs-test.git/objects/a6/7134b8484c2abe9fa954e1fd83b39b271383ed Binary files differnew file mode 100644 index 0000000000..122f87efcc --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/objects/a6/7134b8484c2abe9fa954e1fd83b39b271383ed diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/objects/b7/01ed6ffe410f0c3ac204b929ea47cfec6cef54 b/tests/gitea-repositories-meta/migration/lfs-test.git/objects/b7/01ed6ffe410f0c3ac204b929ea47cfec6cef54 Binary files differnew file mode 100644 index 0000000000..554b7f05b0 --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/objects/b7/01ed6ffe410f0c3ac204b929ea47cfec6cef54 diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/objects/f2/07b74f55cd7f9e800b7550d587cbc488f6eaf1 b/tests/gitea-repositories-meta/migration/lfs-test.git/objects/f2/07b74f55cd7f9e800b7550d587cbc488f6eaf1 Binary files differnew file mode 100644 index 0000000000..ae6fdce5a2 --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/objects/f2/07b74f55cd7f9e800b7550d587cbc488f6eaf1 diff --git a/tests/gitea-repositories-meta/migration/lfs-test.git/refs/heads/master b/tests/gitea-repositories-meta/migration/lfs-test.git/refs/heads/master new file mode 100644 index 0000000000..cd602fb935 --- /dev/null +++ b/tests/gitea-repositories-meta/migration/lfs-test.git/refs/heads/master @@ -0,0 +1 @@ +546244003622c64b2fc3c2cd544d7a29882c8383 diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/COMMITMESSAGE b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/COMMITMESSAGE new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/COMMITMESSAGE diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/COMMIT_EDITMSG b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/COMMIT_EDITMSG new file mode 100644 index 0000000000..5852f44639 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/COMMIT_EDITMSG @@ -0,0 +1 @@ +Initial commit diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/HEAD b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/HEAD new file mode 100644 index 0000000000..4568acfd1d --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/branch1 diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/config b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/config new file mode 100644 index 0000000000..48ee2884b4 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/config @@ -0,0 +1,10 @@ +[core]
+ repositoryformatversion = 0
+ filemode = false
+ bare = false
+ logallrefupdates = true
+ symlinks = false
+ ignorecase = true
+[user]
+ name = user2
+ email = user2@example.com
diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/config.backup b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/config.backup new file mode 100644 index 0000000000..d545cdabdb --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/config.backup @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/description b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/index b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/index Binary files differnew file mode 100644 index 0000000000..450ee4237c --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/index diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/info/exclude b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/logs/HEAD b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/logs/HEAD new file mode 100644 index 0000000000..19ba979e8c --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/logs/HEAD @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491734 +0100 commit (initial): Initial commit +cdaca8cf1d36e1e4e508a940f6e157e239beccfa cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491742 +0100 checkout: moving from master to branch1 diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/logs/refs/heads/branch1 b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/logs/refs/heads/branch1 new file mode 100644 index 0000000000..0501061d19 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/logs/refs/heads/branch1 @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491742 +0100 branch: Created from cdaca8cf1d36e1e4e508a940f6e157e239beccfa diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/logs/refs/heads/master b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/logs/refs/heads/master new file mode 100644 index 0000000000..b67741e6b3 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491734 +0100 commit (initial): Initial commit diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/objects/ba/ea7d6e6b7773a80bcede323cfb21dfe9d4b855 b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/objects/ba/ea7d6e6b7773a80bcede323cfb21dfe9d4b855 Binary files differnew file mode 100644 index 0000000000..fefe858ee7 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/objects/ba/ea7d6e6b7773a80bcede323cfb21dfe9d4b855 diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/objects/c2/a1ad4c931cebe27c7e39176fe7119b5557c9eb b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/objects/c2/a1ad4c931cebe27c7e39176fe7119b5557c9eb Binary files differnew file mode 100644 index 0000000000..c53ae2e3d6 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/objects/c2/a1ad4c931cebe27c7e39176fe7119b5557c9eb diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/objects/cd/aca8cf1d36e1e4e508a940f6e157e239beccfa b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/objects/cd/aca8cf1d36e1e4e508a940f6e157e239beccfa new file mode 100644 index 0000000000..5dce64cf7e --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/objects/cd/aca8cf1d36e1e4e508a940f6e157e239beccfa @@ -0,0 +1,3 @@ +x•K +Â0@]ç³$“ß´ âÖcL’)#1oѸzðàñR«µÀ࣋@daÊAB$"Ë“ŽI²XcÓ
æEæìâä½âmÜ[‡í%ÝÀù‹«¼¹>W9¥V/€ž¼›‘¬ƒ£FÕn÷Õ¿"u{”Qx…_>ÿ +6
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/refs/heads/branch1 b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/refs/heads/branch1 new file mode 100644 index 0000000000..35f8462fa1 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/refs/heads/branch1 @@ -0,0 +1 @@ +cdaca8cf1d36e1e4e508a940f6e157e239beccfa diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker.git/refs/heads/master b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/refs/heads/master new file mode 100644 index 0000000000..35f8462fa1 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker.git/refs/heads/master @@ -0,0 +1 @@ +cdaca8cf1d36e1e4e508a940f6e157e239beccfa diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/COMMITMESSAGE b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/COMMITMESSAGE new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/COMMITMESSAGE diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/COMMIT_EDITMSG b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/COMMIT_EDITMSG new file mode 100644 index 0000000000..5852f44639 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/COMMIT_EDITMSG @@ -0,0 +1 @@ +Initial commit diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/HEAD b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/HEAD new file mode 100644 index 0000000000..4568acfd1d --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/branch1 diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/config b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/config new file mode 100644 index 0000000000..48ee2884b4 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/config @@ -0,0 +1,10 @@ +[core]
+ repositoryformatversion = 0
+ filemode = false
+ bare = false
+ logallrefupdates = true
+ symlinks = false
+ ignorecase = true
+[user]
+ name = user2
+ email = user2@example.com
diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/config.backup b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/config.backup new file mode 100644 index 0000000000..d545cdabdb --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/config.backup @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/description b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/index b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/index Binary files differnew file mode 100644 index 0000000000..450ee4237c --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/index diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/info/exclude b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/logs/HEAD b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/logs/HEAD new file mode 100644 index 0000000000..19ba979e8c --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/logs/HEAD @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491734 +0100 commit (initial): Initial commit +cdaca8cf1d36e1e4e508a940f6e157e239beccfa cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491742 +0100 checkout: moving from master to branch1 diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/logs/refs/heads/branch1 b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/logs/refs/heads/branch1 new file mode 100644 index 0000000000..0501061d19 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/logs/refs/heads/branch1 @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491742 +0100 branch: Created from cdaca8cf1d36e1e4e508a940f6e157e239beccfa diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/logs/refs/heads/master b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/logs/refs/heads/master new file mode 100644 index 0000000000..b67741e6b3 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491734 +0100 commit (initial): Initial commit diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/objects/ba/ea7d6e6b7773a80bcede323cfb21dfe9d4b855 b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/objects/ba/ea7d6e6b7773a80bcede323cfb21dfe9d4b855 Binary files differnew file mode 100644 index 0000000000..fefe858ee7 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/objects/ba/ea7d6e6b7773a80bcede323cfb21dfe9d4b855 diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/objects/c2/a1ad4c931cebe27c7e39176fe7119b5557c9eb b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/objects/c2/a1ad4c931cebe27c7e39176fe7119b5557c9eb Binary files differnew file mode 100644 index 0000000000..c53ae2e3d6 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/objects/c2/a1ad4c931cebe27c7e39176fe7119b5557c9eb diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/objects/cd/aca8cf1d36e1e4e508a940f6e157e239beccfa b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/objects/cd/aca8cf1d36e1e4e508a940f6e157e239beccfa new file mode 100644 index 0000000000..5dce64cf7e --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/objects/cd/aca8cf1d36e1e4e508a940f6e157e239beccfa @@ -0,0 +1,3 @@ +x•K +Â0@]ç³$“ß´ âÖcL’)#1oѸzðàñR«µÀ࣋@daÊAB$"Ë“ŽI²XcÓ
æEæìâä½âmÜ[‡í%ÝÀù‹«¼¹>W9¥V/€ž¼›‘¬ƒ£FÕn÷Õ¿"u{”Qx…_>ÿ +6
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/refs/heads/branch1 b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/refs/heads/branch1 new file mode 100644 index 0000000000..35f8462fa1 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/refs/heads/branch1 @@ -0,0 +1 @@ +cdaca8cf1d36e1e4e508a940f6e157e239beccfa diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/refs/heads/master b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/refs/heads/master new file mode 100644 index 0000000000..35f8462fa1 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_alpha.git/refs/heads/master @@ -0,0 +1 @@ +cdaca8cf1d36e1e4e508a940f6e157e239beccfa diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/COMMITMESSAGE b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/COMMITMESSAGE new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/COMMITMESSAGE diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/COMMIT_EDITMSG b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/COMMIT_EDITMSG new file mode 100644 index 0000000000..5852f44639 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/COMMIT_EDITMSG @@ -0,0 +1 @@ +Initial commit diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/HEAD b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/HEAD new file mode 100644 index 0000000000..4568acfd1d --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/branch1 diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/config b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/config new file mode 100644 index 0000000000..48ee2884b4 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/config @@ -0,0 +1,10 @@ +[core]
+ repositoryformatversion = 0
+ filemode = false
+ bare = false
+ logallrefupdates = true
+ symlinks = false
+ ignorecase = true
+[user]
+ name = user2
+ email = user2@example.com
diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/config.backup b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/config.backup new file mode 100644 index 0000000000..d545cdabdb --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/config.backup @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/description b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/index b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/index Binary files differnew file mode 100644 index 0000000000..450ee4237c --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/index diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/info/exclude b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/logs/HEAD b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/logs/HEAD new file mode 100644 index 0000000000..19ba979e8c --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/logs/HEAD @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491734 +0100 commit (initial): Initial commit +cdaca8cf1d36e1e4e508a940f6e157e239beccfa cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491742 +0100 checkout: moving from master to branch1 diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/logs/refs/heads/branch1 b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/logs/refs/heads/branch1 new file mode 100644 index 0000000000..0501061d19 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/logs/refs/heads/branch1 @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491742 +0100 branch: Created from cdaca8cf1d36e1e4e508a940f6e157e239beccfa diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/logs/refs/heads/master b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/logs/refs/heads/master new file mode 100644 index 0000000000..b67741e6b3 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 cdaca8cf1d36e1e4e508a940f6e157e239beccfa user2 <user2@example.com> 1575491734 +0100 commit (initial): Initial commit diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/objects/ba/ea7d6e6b7773a80bcede323cfb21dfe9d4b855 b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/objects/ba/ea7d6e6b7773a80bcede323cfb21dfe9d4b855 Binary files differnew file mode 100644 index 0000000000..fefe858ee7 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/objects/ba/ea7d6e6b7773a80bcede323cfb21dfe9d4b855 diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/objects/c2/a1ad4c931cebe27c7e39176fe7119b5557c9eb b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/objects/c2/a1ad4c931cebe27c7e39176fe7119b5557c9eb Binary files differnew file mode 100644 index 0000000000..c53ae2e3d6 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/objects/c2/a1ad4c931cebe27c7e39176fe7119b5557c9eb diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/objects/cd/aca8cf1d36e1e4e508a940f6e157e239beccfa b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/objects/cd/aca8cf1d36e1e4e508a940f6e157e239beccfa new file mode 100644 index 0000000000..5dce64cf7e --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/objects/cd/aca8cf1d36e1e4e508a940f6e157e239beccfa @@ -0,0 +1,3 @@ +x•K +Â0@]ç³$“ß´ âÖcL’)#1oѸzðàñR«µÀ࣋@daÊAB$"Ë“ŽI²XcÓ
æEæìâä½âmÜ[‡í%ÝÀù‹«¼¹>W9¥V/€ž¼›‘¬ƒ£FÕn÷Õ¿"u{”Qx…_>ÿ +6
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/refs/heads/branch1 b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/refs/heads/branch1 new file mode 100644 index 0000000000..35f8462fa1 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/refs/heads/branch1 @@ -0,0 +1 @@ +cdaca8cf1d36e1e4e508a940f6e157e239beccfa diff --git a/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/refs/heads/master b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/refs/heads/master new file mode 100644 index 0000000000..35f8462fa1 --- /dev/null +++ b/tests/gitea-repositories-meta/org26/repo_external_tracker_numeric.git/refs/heads/master @@ -0,0 +1 @@ +cdaca8cf1d36e1e4e508a940f6e157e239beccfa diff --git a/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/HEAD b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/config b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/config new file mode 100644 index 0000000000..e6da231579 --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true diff --git a/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/description b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/info/exclude b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/objects/6e/75c9f89da9a9b93f4f36e61ed092f7a1625ba0 b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/objects/6e/75c9f89da9a9b93f4f36e61ed092f7a1625ba0 Binary files differnew file mode 100644 index 0000000000..9db794ca36 --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/objects/6e/75c9f89da9a9b93f4f36e61ed092f7a1625ba0 diff --git a/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/objects/7f/eb6f9dd600e17a04f48a76cfa0a56a3f30e2c1 b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/objects/7f/eb6f9dd600e17a04f48a76cfa0a56a3f30e2c1 Binary files differnew file mode 100644 index 0000000000..c219deb1b3 --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/objects/7f/eb6f9dd600e17a04f48a76cfa0a56a3f30e2c1 diff --git a/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/objects/b7/91b41c0ae8cb3c4b12f3fd8c3709c2481d9e37 b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/objects/b7/91b41c0ae8cb3c4b12f3fd8c3709c2481d9e37 Binary files differnew file mode 100644 index 0000000000..60b507e9ca --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/objects/b7/91b41c0ae8cb3c4b12f3fd8c3709c2481d9e37 diff --git a/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/refs/heads/master b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/refs/heads/master new file mode 100644 index 0000000000..64e40736d6 --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/private_repo_on_private_org.git/refs/heads/master @@ -0,0 +1 @@ +6e75c9f89da9a9b93f4f36e61ed092f7a1625ba0 diff --git a/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/HEAD b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/config b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/config new file mode 100644 index 0000000000..e6da231579 --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true diff --git a/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/description b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/info/exclude b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/objects/04/f99c528b643b9175a4b156cdfc13aba6b43853 b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/objects/04/f99c528b643b9175a4b156cdfc13aba6b43853 Binary files differnew file mode 100644 index 0000000000..e97437c599 --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/objects/04/f99c528b643b9175a4b156cdfc13aba6b43853 diff --git a/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/objects/86/de16d8658f5c0a17ec6aa313871295d7072f78 b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/objects/86/de16d8658f5c0a17ec6aa313871295d7072f78 Binary files differnew file mode 100644 index 0000000000..169f196579 --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/objects/86/de16d8658f5c0a17ec6aa313871295d7072f78 diff --git a/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/objects/bf/19fd4707acb403c4aca44f126ab69142ac59ce b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/objects/bf/19fd4707acb403c4aca44f126ab69142ac59ce Binary files differnew file mode 100644 index 0000000000..d04c6cb3bf --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/objects/bf/19fd4707acb403c4aca44f126ab69142ac59ce diff --git a/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/refs/heads/master b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/refs/heads/master new file mode 100644 index 0000000000..56af246fff --- /dev/null +++ b/tests/gitea-repositories-meta/privated_org/public_repo_on_private_org.git/refs/heads/master @@ -0,0 +1 @@ +bf19fd4707acb403c4aca44f126ab69142ac59ce diff --git a/tests/gitea-repositories-meta/user12/repo10.git/HEAD b/tests/gitea-repositories-meta/user12/repo10.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user12/repo10.git/config b/tests/gitea-repositories-meta/user12/repo10.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/tests/gitea-repositories-meta/user12/repo10.git/description b/tests/gitea-repositories-meta/user12/repo10.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user12/repo10.git/hooks/post-receive b/tests/gitea-repositories-meta/user12/repo10.git/hooks/post-receive new file mode 100755 index 0000000000..4b3d452abc --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/hooks/post-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/post-receive.d"`; do + sh "$SHELL_FOLDER/post-receive.d/$i" +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user12/repo10.git/hooks/post-receive.d/gitea b/tests/gitea-repositories-meta/user12/repo10.git/hooks/post-receive.d/gitea new file mode 100755 index 0000000000..43a948da3a --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/hooks/post-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive diff --git a/tests/gitea-repositories-meta/user12/repo10.git/hooks/pre-receive b/tests/gitea-repositories-meta/user12/repo10.git/hooks/pre-receive new file mode 100755 index 0000000000..4127013053 --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/hooks/pre-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/pre-receive.d"`; do + sh "$SHELL_FOLDER/pre-receive.d/$i" +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user12/repo10.git/hooks/pre-receive.d/gitea b/tests/gitea-repositories-meta/user12/repo10.git/hooks/pre-receive.d/gitea new file mode 100755 index 0000000000..49d0940636 --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/hooks/pre-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive diff --git a/tests/gitea-repositories-meta/user12/repo10.git/hooks/update b/tests/gitea-repositories-meta/user12/repo10.git/hooks/update new file mode 100755 index 0000000000..c186fe4a18 --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/hooks/update @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/update.d"`; do + sh "$SHELL_FOLDER/update.d/$i" $1 $2 $3 +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user12/repo10.git/hooks/update.d/gitea b/tests/gitea-repositories-meta/user12/repo10.git/hooks/update.d/gitea new file mode 100755 index 0000000000..38101c2426 --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/hooks/update.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3 diff --git a/tests/gitea-repositories-meta/user12/repo10.git/info/exclude b/tests/gitea-repositories-meta/user12/repo10.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user12/repo10.git/info/refs b/tests/gitea-repositories-meta/user12/repo10.git/info/refs new file mode 100644 index 0000000000..ca1df85e2e --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/info/refs @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/master diff --git a/tests/gitea-repositories-meta/user12/repo10.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 b/tests/gitea-repositories-meta/user12/repo10.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 Binary files differnew file mode 100644 index 0000000000..0994add2c8 --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 diff --git a/tests/gitea-repositories-meta/user12/repo10.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f b/tests/gitea-repositories-meta/user12/repo10.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f Binary files differnew file mode 100644 index 0000000000..700a13828e --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f diff --git a/tests/gitea-repositories-meta/user12/repo10.git/objects/65/f1bf27bc3bf70f64657658635e66094edbcb4d b/tests/gitea-repositories-meta/user12/repo10.git/objects/65/f1bf27bc3bf70f64657658635e66094edbcb4d Binary files differnew file mode 100644 index 0000000000..de48ba79f5 --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/objects/65/f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user12/repo10.git/objects/info/packs b/tests/gitea-repositories-meta/user12/repo10.git/objects/info/packs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/objects/info/packs @@ -0,0 +1 @@ + diff --git a/tests/gitea-repositories-meta/user12/repo10.git/refs/heads/DefaultBranch b/tests/gitea-repositories-meta/user12/repo10.git/refs/heads/DefaultBranch new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/refs/heads/DefaultBranch @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user12/repo10.git/refs/heads/develop b/tests/gitea-repositories-meta/user12/repo10.git/refs/heads/develop new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/refs/heads/develop @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user12/repo10.git/refs/heads/feature/1 b/tests/gitea-repositories-meta/user12/repo10.git/refs/heads/feature/1 new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/refs/heads/feature/1 @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user12/repo10.git/refs/heads/master b/tests/gitea-repositories-meta/user12/repo10.git/refs/heads/master new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/refs/heads/master @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user12/repo10.git/refs/tags/v1.1 b/tests/gitea-repositories-meta/user12/repo10.git/refs/tags/v1.1 new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user12/repo10.git/refs/tags/v1.1 @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user13/repo11.git/HEAD b/tests/gitea-repositories-meta/user13/repo11.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user13/repo11.git/config b/tests/gitea-repositories-meta/user13/repo11.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/tests/gitea-repositories-meta/user13/repo11.git/description b/tests/gitea-repositories-meta/user13/repo11.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user13/repo11.git/hooks/post-receive b/tests/gitea-repositories-meta/user13/repo11.git/hooks/post-receive new file mode 100755 index 0000000000..4b3d452abc --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/hooks/post-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/post-receive.d"`; do + sh "$SHELL_FOLDER/post-receive.d/$i" +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user13/repo11.git/hooks/post-receive.d/gitea b/tests/gitea-repositories-meta/user13/repo11.git/hooks/post-receive.d/gitea new file mode 100755 index 0000000000..43a948da3a --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/hooks/post-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive diff --git a/tests/gitea-repositories-meta/user13/repo11.git/hooks/pre-receive b/tests/gitea-repositories-meta/user13/repo11.git/hooks/pre-receive new file mode 100755 index 0000000000..4127013053 --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/hooks/pre-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/pre-receive.d"`; do + sh "$SHELL_FOLDER/pre-receive.d/$i" +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user13/repo11.git/hooks/pre-receive.d/gitea b/tests/gitea-repositories-meta/user13/repo11.git/hooks/pre-receive.d/gitea new file mode 100755 index 0000000000..49d0940636 --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/hooks/pre-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive diff --git a/tests/gitea-repositories-meta/user13/repo11.git/hooks/update b/tests/gitea-repositories-meta/user13/repo11.git/hooks/update new file mode 100755 index 0000000000..c186fe4a18 --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/hooks/update @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/update.d"`; do + sh "$SHELL_FOLDER/update.d/$i" $1 $2 $3 +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user13/repo11.git/hooks/update.d/gitea b/tests/gitea-repositories-meta/user13/repo11.git/hooks/update.d/gitea new file mode 100755 index 0000000000..38101c2426 --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/hooks/update.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3 diff --git a/tests/gitea-repositories-meta/user13/repo11.git/info/exclude b/tests/gitea-repositories-meta/user13/repo11.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user13/repo11.git/info/refs b/tests/gitea-repositories-meta/user13/repo11.git/info/refs new file mode 100644 index 0000000000..ca1df85e2e --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/info/refs @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/master diff --git a/tests/gitea-repositories-meta/user13/repo11.git/objects/0a/bcb056019adb8336cf9db3ad9d9cf80cd4b141 b/tests/gitea-repositories-meta/user13/repo11.git/objects/0a/bcb056019adb8336cf9db3ad9d9cf80cd4b141 Binary files differnew file mode 100644 index 0000000000..63ba4ed8ee --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/objects/0a/bcb056019adb8336cf9db3ad9d9cf80cd4b141 diff --git a/tests/gitea-repositories-meta/user13/repo11.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 b/tests/gitea-repositories-meta/user13/repo11.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 Binary files differnew file mode 100644 index 0000000000..0994add2c8 --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 diff --git a/tests/gitea-repositories-meta/user13/repo11.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f b/tests/gitea-repositories-meta/user13/repo11.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f Binary files differnew file mode 100644 index 0000000000..700a13828e --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f diff --git a/tests/gitea-repositories-meta/user13/repo11.git/objects/65/f1bf27bc3bf70f64657658635e66094edbcb4d b/tests/gitea-repositories-meta/user13/repo11.git/objects/65/f1bf27bc3bf70f64657658635e66094edbcb4d Binary files differnew file mode 100644 index 0000000000..de48ba79f5 --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/objects/65/f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user13/repo11.git/objects/75/d1afd00e111c8dbd9e3d96a27b431ac5ae6d74 b/tests/gitea-repositories-meta/user13/repo11.git/objects/75/d1afd00e111c8dbd9e3d96a27b431ac5ae6d74 Binary files differnew file mode 100644 index 0000000000..609b1c0e64 --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/objects/75/d1afd00e111c8dbd9e3d96a27b431ac5ae6d74 diff --git a/tests/gitea-repositories-meta/user13/repo11.git/objects/ed/447543e0c85d628b91f7f466f4921908f4c5ea b/tests/gitea-repositories-meta/user13/repo11.git/objects/ed/447543e0c85d628b91f7f466f4921908f4c5ea Binary files differnew file mode 100644 index 0000000000..b3a1046fee --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/objects/ed/447543e0c85d628b91f7f466f4921908f4c5ea diff --git a/tests/gitea-repositories-meta/user13/repo11.git/objects/info/packs b/tests/gitea-repositories-meta/user13/repo11.git/objects/info/packs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/objects/info/packs @@ -0,0 +1 @@ + diff --git a/tests/gitea-repositories-meta/user13/repo11.git/refs/heads/DefaultBranch b/tests/gitea-repositories-meta/user13/repo11.git/refs/heads/DefaultBranch new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/refs/heads/DefaultBranch @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user13/repo11.git/refs/heads/branch2 b/tests/gitea-repositories-meta/user13/repo11.git/refs/heads/branch2 new file mode 100644 index 0000000000..d413449668 --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/refs/heads/branch2 @@ -0,0 +1 @@ +0abcb056019adb8336cf9db3ad9d9cf80cd4b141 diff --git a/tests/gitea-repositories-meta/user13/repo11.git/refs/heads/develop b/tests/gitea-repositories-meta/user13/repo11.git/refs/heads/develop new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/refs/heads/develop @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user13/repo11.git/refs/heads/feature/1 b/tests/gitea-repositories-meta/user13/repo11.git/refs/heads/feature/1 new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/refs/heads/feature/1 @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user13/repo11.git/refs/heads/master b/tests/gitea-repositories-meta/user13/repo11.git/refs/heads/master new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/refs/heads/master @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user13/repo11.git/refs/tags/v1.1 b/tests/gitea-repositories-meta/user13/repo11.git/refs/tags/v1.1 new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user13/repo11.git/refs/tags/v1.1 @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/HEAD b/tests/gitea-repositories-meta/user2/commits_search_test.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/config b/tests/gitea-repositories-meta/user2/commits_search_test.git/config new file mode 100644 index 0000000000..bfbada5989 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true +[remote "origin"] + url = /home/mura/go/src/code.gitea.io/gitea/tests/gitea-repositories-meta/user2/commits_search_test/ + fetch = +refs/*:refs/* + mirror = true diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/description b/tests/gitea-repositories-meta/user2/commits_search_test.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/info/exclude b/tests/gitea-repositories-meta/user2/commits_search_test.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/0a/8499a22ad32a80beda9d75efe15f9f94582468 b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/0a/8499a22ad32a80beda9d75efe15f9f94582468 new file mode 100644 index 0000000000..c258fcdd3a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/0a/8499a22ad32a80beda9d75efe15f9f94582468 @@ -0,0 +1,2 @@ +x…ŽÍ +Â0„=÷)ö.Ê&›Ÿ-ˆè;øq³ÁBcK‰àãÄ»—™aàF–Z§i×6UÈ!)£¥d°çÂ=$§±'ã½crˆ¬ã(4¬iÓg±(©D¾û2æl3fÃÄ1ˆ—ŒƒC%Ò«=–
nK…S—‹¾S]g=ÊRÏÐÇç@daŒ8ô¶ßjºÁužDáôµ?Ð:Ðð?‹>9
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/0c/cf1fcd4d1717c22de0707619a5577ea0acebf0 b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/0c/cf1fcd4d1717c22de0707619a5577ea0acebf0 Binary files differnew file mode 100644 index 0000000000..25b730dc81 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/0c/cf1fcd4d1717c22de0707619a5577ea0acebf0 diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/3e/a192a6466793d4b7cd8641801ca0c6bec3979c b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/3e/a192a6466793d4b7cd8641801ca0c6bec3979c Binary files differnew file mode 100644 index 0000000000..69b09b5129 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/3e/a192a6466793d4b7cd8641801ca0c6bec3979c diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/3f/6594f108842b7c50772510e53ce113d3583c4a b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/3f/6594f108842b7c50772510e53ce113d3583c4a Binary files differnew file mode 100644 index 0000000000..18a2af07c8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/3f/6594f108842b7c50772510e53ce113d3583c4a diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/58/e97d1a24fb9e1599d8a467ec409430f3d3569e b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/58/e97d1a24fb9e1599d8a467ec409430f3d3569e Binary files differnew file mode 100644 index 0000000000..585bb66e5b --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/58/e97d1a24fb9e1599d8a467ec409430f3d3569e diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/67/68c1fc1d9448422f05cc84267d94ee62085fe8 b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/67/68c1fc1d9448422f05cc84267d94ee62085fe8 Binary files differnew file mode 100644 index 0000000000..2860a7c559 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/67/68c1fc1d9448422f05cc84267d94ee62085fe8 diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/6e/8eabd9a7f8d6acd2a1219facfd37415564b144 b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/6e/8eabd9a7f8d6acd2a1219facfd37415564b144 new file mode 100644 index 0000000000..4fb1314493 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/6e/8eabd9a7f8d6acd2a1219facfd37415564b144 @@ -0,0 +1,2 @@ +x•[ +1Eýî*ò/J2é+ ¢KédL©\¾EÜ€_.œ{´Õºv ‡¾›[!™Jô1&áÅÏI—=e$-¨q6eI¢®¼ú£ípßV5¸|q³w©ÏÍÎÚêu\òÄ'8bFtc©nI?ëDîêç1â
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/85/f46d747a68adf79cc01e2c25ba6a56932d298d b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/85/f46d747a68adf79cc01e2c25ba6a56932d298d Binary files differnew file mode 100644 index 0000000000..f276d2f694 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/85/f46d747a68adf79cc01e2c25ba6a56932d298d diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/8d/dd8d1ad1fdc21ab629e906711fa9bc27aa1c52 b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/8d/dd8d1ad1fdc21ab629e906711fa9bc27aa1c52 Binary files differnew file mode 100644 index 0000000000..00c3a450a2 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/8d/dd8d1ad1fdc21ab629e906711fa9bc27aa1c52 diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/95/fd0c4138480e4b3913e7cf71a90623fb926fe8 b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/95/fd0c4138480e4b3913e7cf71a90623fb926fe8 Binary files differnew file mode 100644 index 0000000000..2dd5fc2dfe --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/95/fd0c4138480e4b3913e7cf71a90623fb926fe8 diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/98/00fe78cabf4fe774fcf376f97fa2a0ed06987b b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/98/00fe78cabf4fe774fcf376f97fa2a0ed06987b Binary files differnew file mode 100644 index 0000000000..42a85f1b77 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/98/00fe78cabf4fe774fcf376f97fa2a0ed06987b diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/9f/cdb7d53bdef786d2e5577948a0c0d4b321fe5a b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/9f/cdb7d53bdef786d2e5577948a0c0d4b321fe5a Binary files differnew file mode 100644 index 0000000000..125bc6c9e1 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/9f/cdb7d53bdef786d2e5577948a0c0d4b321fe5a diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/c2/0caf78b5f9dd2d0d183876c5cd0e761c13f7f8 b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/c2/0caf78b5f9dd2d0d183876c5cd0e761c13f7f8 new file mode 100644 index 0000000000..83e681f9da --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/c2/0caf78b5f9dd2d0d183876c5cd0e761c13f7f8 @@ -0,0 +1,2 @@ +xŽK +1]çÙ‹’ÎoÒ0ˆx“NÒ‚1ÃÁã›…póE¼Ò[{
ˆ‡±3ël¦ÅÇ$”T:–œ³K°±ÀPeQíü:rbÊi‘T#•j , P‘ê!DŸÁ{Eïq﻾õ¬×9WþPÛž|.½]ôÔÀ#¢³úh’1jÒykðßÁ¯8YõE=Î
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/c5/2ba74685f5c8c593efbbb38f62fe024110adef b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/c5/2ba74685f5c8c593efbbb38f62fe024110adef Binary files differnew file mode 100644 index 0000000000..eef09bf6fc --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/c5/2ba74685f5c8c593efbbb38f62fe024110adef diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/d6/ae8023a10ff446b6a4e7f441554834008e99c3 b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/d6/ae8023a10ff446b6a4e7f441554834008e99c3 Binary files differnew file mode 100644 index 0000000000..99f8189587 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/objects/d6/ae8023a10ff446b6a4e7f441554834008e99c3 diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/packed-refs b/tests/gitea-repositories-meta/user2/commits_search_test.git/packed-refs new file mode 100644 index 0000000000..7675cfd15f --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled +9800fe78cabf4fe774fcf376f97fa2a0ed06987b refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/commits_search_test.git/refs/heads/master b/tests/gitea-repositories-meta/user2/commits_search_test.git/refs/heads/master new file mode 100644 index 0000000000..81ba7e721a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/commits_search_test.git/refs/heads/master @@ -0,0 +1 @@ +9800fe78cabf4fe774fcf376f97fa2a0ed06987b diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/HEAD b/tests/gitea-repositories-meta/user2/git_hooks_test.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/config b/tests/gitea-repositories-meta/user2/git_hooks_test.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/description b/tests/gitea-repositories-meta/user2/git_hooks_test.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/post-receive b/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/post-receive new file mode 100755 index 0000000000..4b3d452abc --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/post-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/post-receive.d"`; do + sh "$SHELL_FOLDER/post-receive.d/$i" +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/post-receive.d/gitea b/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/post-receive.d/gitea new file mode 100755 index 0000000000..43a948da3a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/post-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/pre-receive b/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/pre-receive new file mode 100755 index 0000000000..4127013053 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/pre-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/pre-receive.d"`; do + sh "$SHELL_FOLDER/pre-receive.d/$i" +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/pre-receive.d/gitea b/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/pre-receive.d/gitea new file mode 100755 index 0000000000..49d0940636 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/pre-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/pre-receive.d/pre-receive b/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/pre-receive.d/pre-receive new file mode 100755 index 0000000000..b26a3b9b68 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/pre-receive.d/pre-receive @@ -0,0 +1,3 @@ +#!/bin/bash + +echo Hello, World! diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/update b/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/update new file mode 100755 index 0000000000..c186fe4a18 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/update @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/update.d"`; do + sh "$SHELL_FOLDER/update.d/$i" $1 $2 $3 +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/update.d/gitea b/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/update.d/gitea new file mode 100755 index 0000000000..38101c2426 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/hooks/update.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3 diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/info/exclude b/tests/gitea-repositories-meta/user2/git_hooks_test.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/info/refs b/tests/gitea-repositories-meta/user2/git_hooks_test.git/info/refs new file mode 100644 index 0000000000..ca1df85e2e --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/info/refs @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 b/tests/gitea-repositories-meta/user2/git_hooks_test.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 Binary files differnew file mode 100644 index 0000000000..0994add2c8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f b/tests/gitea-repositories-meta/user2/git_hooks_test.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f Binary files differnew file mode 100644 index 0000000000..700a13828e --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/objects/65/f1bf27bc3bf70f64657658635e66094edbcb4d b/tests/gitea-repositories-meta/user2/git_hooks_test.git/objects/65/f1bf27bc3bf70f64657658635e66094edbcb4d Binary files differnew file mode 100644 index 0000000000..de48ba79f5 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/objects/65/f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/objects/info/packs b/tests/gitea-repositories-meta/user2/git_hooks_test.git/objects/info/packs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/objects/info/packs @@ -0,0 +1 @@ + diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/refs/heads/DefaultBranch b/tests/gitea-repositories-meta/user2/git_hooks_test.git/refs/heads/DefaultBranch new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/refs/heads/DefaultBranch @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/refs/heads/develop b/tests/gitea-repositories-meta/user2/git_hooks_test.git/refs/heads/develop new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/refs/heads/develop @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/refs/heads/feature/1 b/tests/gitea-repositories-meta/user2/git_hooks_test.git/refs/heads/feature/1 new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/refs/heads/feature/1 @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/refs/heads/master b/tests/gitea-repositories-meta/user2/git_hooks_test.git/refs/heads/master new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/refs/heads/master @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user2/git_hooks_test.git/refs/tags/v1.1 b/tests/gitea-repositories-meta/user2/git_hooks_test.git/refs/tags/v1.1 new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/git_hooks_test.git/refs/tags/v1.1 @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user2/glob.git/HEAD b/tests/gitea-repositories-meta/user2/glob.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/glob.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/glob.git/config b/tests/gitea-repositories-meta/user2/glob.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/glob.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/tests/gitea-repositories-meta/user2/glob.git/description b/tests/gitea-repositories-meta/user2/glob.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/glob.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user2/glob.git/info/exclude b/tests/gitea-repositories-meta/user2/glob.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/glob.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user2/glob.git/objects/48/06cb9df135782b818c968c2fadbd2c150d23d6 b/tests/gitea-repositories-meta/user2/glob.git/objects/48/06cb9df135782b818c968c2fadbd2c150d23d6 Binary files differnew file mode 100644 index 0000000000..a393a43282 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/glob.git/objects/48/06cb9df135782b818c968c2fadbd2c150d23d6 diff --git a/tests/gitea-repositories-meta/user2/glob.git/objects/59/fee614e09d1f1cd1e15e4b2a7e9c8873a81498 b/tests/gitea-repositories-meta/user2/glob.git/objects/59/fee614e09d1f1cd1e15e4b2a7e9c8873a81498 Binary files differnew file mode 100644 index 0000000000..a55c8cc6e6 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/glob.git/objects/59/fee614e09d1f1cd1e15e4b2a7e9c8873a81498 diff --git a/tests/gitea-repositories-meta/user2/glob.git/objects/7c/8ac2f8d82a1eb5f6aaece6629ff11015f91eb4 b/tests/gitea-repositories-meta/user2/glob.git/objects/7c/8ac2f8d82a1eb5f6aaece6629ff11015f91eb4 Binary files differnew file mode 100644 index 0000000000..d5176e68c6 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/glob.git/objects/7c/8ac2f8d82a1eb5f6aaece6629ff11015f91eb4 diff --git a/tests/gitea-repositories-meta/user2/glob.git/objects/8e/592e636d27ac144f92f7fe8c33631cbdea594d b/tests/gitea-repositories-meta/user2/glob.git/objects/8e/592e636d27ac144f92f7fe8c33631cbdea594d Binary files differnew file mode 100644 index 0000000000..8034110ebc --- /dev/null +++ b/tests/gitea-repositories-meta/user2/glob.git/objects/8e/592e636d27ac144f92f7fe8c33631cbdea594d diff --git a/tests/gitea-repositories-meta/user2/glob.git/objects/95/aff026f99a9ab76fbd01decb63dd3dbc03e498 b/tests/gitea-repositories-meta/user2/glob.git/objects/95/aff026f99a9ab76fbd01decb63dd3dbc03e498 Binary files differnew file mode 100644 index 0000000000..0883f2b3eb --- /dev/null +++ b/tests/gitea-repositories-meta/user2/glob.git/objects/95/aff026f99a9ab76fbd01decb63dd3dbc03e498 diff --git a/tests/gitea-repositories-meta/user2/glob.git/objects/ae/d1ffed24cc3cf9b80490795e893cae4bddd684 b/tests/gitea-repositories-meta/user2/glob.git/objects/ae/d1ffed24cc3cf9b80490795e893cae4bddd684 Binary files differnew file mode 100644 index 0000000000..03fa05de22 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/glob.git/objects/ae/d1ffed24cc3cf9b80490795e893cae4bddd684 diff --git a/tests/gitea-repositories-meta/user2/glob.git/objects/bf/d6a6583f9a9ac59bd726c1df26c64a89427ede b/tests/gitea-repositories-meta/user2/glob.git/objects/bf/d6a6583f9a9ac59bd726c1df26c64a89427ede Binary files differnew file mode 100644 index 0000000000..9475433e66 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/glob.git/objects/bf/d6a6583f9a9ac59bd726c1df26c64a89427ede diff --git a/tests/gitea-repositories-meta/user2/glob.git/objects/c8/eb3b6c767ccb68411d0a1f6c769be69fb4d95a b/tests/gitea-repositories-meta/user2/glob.git/objects/c8/eb3b6c767ccb68411d0a1f6c769be69fb4d95a new file mode 100644 index 0000000000..2b6297f726 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/glob.git/objects/c8/eb3b6c767ccb68411d0a1f6c769be69fb4d95a @@ -0,0 +1 @@ +x+)JMU03d040031QHÒ+©(a¨é:ô㆖ÜÖo«Þ<KšÿQ@ô§Ü P¨dè‹ÔKÎU_#â?éû¿ãd™½¯"}•‡
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/glob.git/objects/de/6be43fe8eb19ca3f4e934cb8b9a9a0b20fe865 b/tests/gitea-repositories-meta/user2/glob.git/objects/de/6be43fe8eb19ca3f4e934cb8b9a9a0b20fe865 Binary files differnew file mode 100644 index 0000000000..ece04b3974 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/glob.git/objects/de/6be43fe8eb19ca3f4e934cb8b9a9a0b20fe865 diff --git a/tests/gitea-repositories-meta/user2/glob.git/objects/ef/6b814b610d8e7717aa0f71fbe5842bcf814697 b/tests/gitea-repositories-meta/user2/glob.git/objects/ef/6b814b610d8e7717aa0f71fbe5842bcf814697 new file mode 100644 index 0000000000..264cf5a196 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/glob.git/objects/ef/6b814b610d8e7717aa0f71fbe5842bcf814697 @@ -0,0 +1,2 @@ +xÎ9Â0@QjŸb.@ä8“MB*Z®àxÆÁÂŽ#/·'â´¯øú&†à +(ÕžJbÍÔZˤИÎØy™$Îrœ{žæÎhÆ…ˆ† …®å<ªóžSˆðLz#—õ'»zßjæ”›-&Þý§Y]yÕ¥11\¡í‡q@D…p–”âÐc£ðß‚âN…së<gñ¿ŽK©
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/glob.git/refs/heads/master b/tests/gitea-repositories-meta/user2/glob.git/refs/heads/master new file mode 100644 index 0000000000..bca1628455 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/glob.git/refs/heads/master @@ -0,0 +1 @@ +ef6b814b610d8e7717aa0f71fbe5842bcf814697 diff --git a/tests/gitea-repositories-meta/user2/repo1.git/HEAD b/tests/gitea-repositories-meta/user2/repo1.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/repo1.git/config b/tests/gitea-repositories-meta/user2/repo1.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/tests/gitea-repositories-meta/user2/repo1.git/description b/tests/gitea-repositories-meta/user2/repo1.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user2/repo1.git/hooks/post-receive b/tests/gitea-repositories-meta/user2/repo1.git/hooks/post-receive new file mode 100755 index 0000000000..4b3d452abc --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/hooks/post-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/post-receive.d"`; do + sh "$SHELL_FOLDER/post-receive.d/$i" +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo1.git/hooks/post-receive.d/gitea b/tests/gitea-repositories-meta/user2/repo1.git/hooks/post-receive.d/gitea new file mode 100755 index 0000000000..43a948da3a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/hooks/post-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive diff --git a/tests/gitea-repositories-meta/user2/repo1.git/hooks/pre-receive b/tests/gitea-repositories-meta/user2/repo1.git/hooks/pre-receive new file mode 100755 index 0000000000..4127013053 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/hooks/pre-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/pre-receive.d"`; do + sh "$SHELL_FOLDER/pre-receive.d/$i" +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo1.git/hooks/pre-receive.d/gitea b/tests/gitea-repositories-meta/user2/repo1.git/hooks/pre-receive.d/gitea new file mode 100755 index 0000000000..49d0940636 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/hooks/pre-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive diff --git a/tests/gitea-repositories-meta/user2/repo1.git/hooks/update b/tests/gitea-repositories-meta/user2/repo1.git/hooks/update new file mode 100755 index 0000000000..c186fe4a18 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/hooks/update @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/update.d"`; do + sh "$SHELL_FOLDER/update.d/$i" $1 $2 $3 +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo1.git/hooks/update.d/gitea b/tests/gitea-repositories-meta/user2/repo1.git/hooks/update.d/gitea new file mode 100755 index 0000000000..38101c2426 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/hooks/update.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3 diff --git a/tests/gitea-repositories-meta/user2/repo1.git/info/exclude b/tests/gitea-repositories-meta/user2/repo1.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user2/repo1.git/info/refs b/tests/gitea-repositories-meta/user2/repo1.git/info/refs new file mode 100644 index 0000000000..fa3009793d --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/info/refs @@ -0,0 +1,3 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/master +985f0301dba5e7b34be866819cd15ad3d8f508ee refs/heads/branch2 +62fb502a7172d4453f0322a2cc85bddffa57f07a refs/heads/pr-to-update diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/00/750edc07d6415dcc07ae0351e9397b0222b7ba b/tests/gitea-repositories-meta/user2/repo1.git/objects/00/750edc07d6415dcc07ae0351e9397b0222b7ba Binary files differnew file mode 100644 index 0000000000..d3c45d51ea --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/00/750edc07d6415dcc07ae0351e9397b0222b7ba diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 b/tests/gitea-repositories-meta/user2/repo1.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 Binary files differnew file mode 100644 index 0000000000..0994add2c8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/2a/2f1d4670728a2e10049e345bd7a276468beab6 diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/3f/a2f829675543ecfc16b2891aebe8bf0608a8f4 b/tests/gitea-repositories-meta/user2/repo1.git/objects/3f/a2f829675543ecfc16b2891aebe8bf0608a8f4 Binary files differnew file mode 100644 index 0000000000..892c6bffe9 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/3f/a2f829675543ecfc16b2891aebe8bf0608a8f4 diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/4a/357436d925b5c974181ff12a994538ddc5a269 b/tests/gitea-repositories-meta/user2/repo1.git/objects/4a/357436d925b5c974181ff12a994538ddc5a269 Binary files differnew file mode 100644 index 0000000000..bf97d00fd8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/4a/357436d925b5c974181ff12a994538ddc5a269 diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f b/tests/gitea-repositories-meta/user2/repo1.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f Binary files differnew file mode 100644 index 0000000000..700a13828e --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/4b/4851ad51df6a7d9f25c979345979eaeb5b349f diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/5c/050d3b6d2db231ab1f64e324f1b6b9a0b181c2 b/tests/gitea-repositories-meta/user2/repo1.git/objects/5c/050d3b6d2db231ab1f64e324f1b6b9a0b181c2 Binary files differnew file mode 100644 index 0000000000..c0cb626359 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/5c/050d3b6d2db231ab1f64e324f1b6b9a0b181c2 diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/5f/22f7d0d95d614d25a5b68592adb345a4b5c7fd b/tests/gitea-repositories-meta/user2/repo1.git/objects/5f/22f7d0d95d614d25a5b68592adb345a4b5c7fd Binary files differnew file mode 100644 index 0000000000..17fdf18edb --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/5f/22f7d0d95d614d25a5b68592adb345a4b5c7fd diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/62/fb502a7172d4453f0322a2cc85bddffa57f07a b/tests/gitea-repositories-meta/user2/repo1.git/objects/62/fb502a7172d4453f0322a2cc85bddffa57f07a Binary files differnew file mode 100644 index 0000000000..ee494a8ca8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/62/fb502a7172d4453f0322a2cc85bddffa57f07a diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/65/f1bf27bc3bf70f64657658635e66094edbcb4d b/tests/gitea-repositories-meta/user2/repo1.git/objects/65/f1bf27bc3bf70f64657658635e66094edbcb4d Binary files differnew file mode 100644 index 0000000000..de48ba79f5 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/65/f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/6a/a3a5385611c5eb8986c9961a9c34a93cbaadfb b/tests/gitea-repositories-meta/user2/repo1.git/objects/6a/a3a5385611c5eb8986c9961a9c34a93cbaadfb Binary files differnew file mode 100644 index 0000000000..09aed946f2 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/6a/a3a5385611c5eb8986c9961a9c34a93cbaadfb diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/7c/4df115542e05c700f297519e906fd63c9c9804 b/tests/gitea-repositories-meta/user2/repo1.git/objects/7c/4df115542e05c700f297519e906fd63c9c9804 Binary files differnew file mode 100644 index 0000000000..3bf67a206a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/7c/4df115542e05c700f297519e906fd63c9c9804 diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/90/dcd07da077d1e7cd6032b52d1f79ae2b5f19b2 b/tests/gitea-repositories-meta/user2/repo1.git/objects/90/dcd07da077d1e7cd6032b52d1f79ae2b5f19b2 new file mode 100644 index 0000000000..1404abdaa2 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/90/dcd07da077d1e7cd6032b52d1f79ae2b5f19b2 @@ -0,0 +1,2 @@ +xe±NÄ0D©#åæŽ4 +JÄAÅ5”Ž³—,—xÑzsVþ‚5„DåÑØ»ž7ý,=®®o.áEå¢áq5J=éˆýÈ rÄ=>4§ú
O!óŠýã´ðÐ6ms˜8ƒ¾&\Ea¾tÍT„´I¢z”‰Ô…! ¢dso@a›Ú&ÌK5üB)›r4–”Q¦`YèLÚ¯²b ›¾o`Ûaä3¹@(ÒeýÔ5
ôÂH—\sÔHÿ9Ÿ9Rª3)Îë@ŽSùã_"§‘4sE0”Rºñ§¤.‘U|/€m¦Û¿]U÷ÌzÀ
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/94/922e1295c678267de1193b7b84ad8a086c27f9 b/tests/gitea-repositories-meta/user2/repo1.git/objects/94/922e1295c678267de1193b7b84ad8a086c27f9 Binary files differnew file mode 100644 index 0000000000..60692df6ec --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/94/922e1295c678267de1193b7b84ad8a086c27f9 diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/98/5f0301dba5e7b34be866819cd15ad3d8f508ee b/tests/gitea-repositories-meta/user2/repo1.git/objects/98/5f0301dba5e7b34be866819cd15ad3d8f508ee Binary files differnew file mode 100644 index 0000000000..81fd6a50fd --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/98/5f0301dba5e7b34be866819cd15ad3d8f508ee diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/a6/9277c81e90b98a7c0ab25b042a6e296da8eb9a b/tests/gitea-repositories-meta/user2/repo1.git/objects/a6/9277c81e90b98a7c0ab25b042a6e296da8eb9a Binary files differnew file mode 100644 index 0000000000..887669883b --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/a6/9277c81e90b98a7c0ab25b042a6e296da8eb9a diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/a7/57c0ea621e63d0fd6fc353a175fdc7199e5d1d b/tests/gitea-repositories-meta/user2/repo1.git/objects/a7/57c0ea621e63d0fd6fc353a175fdc7199e5d1d Binary files differnew file mode 100644 index 0000000000..c3111a08b8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/a7/57c0ea621e63d0fd6fc353a175fdc7199e5d1d diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/b2/60587271671842af0b036e4fe643c9d45b7ddd b/tests/gitea-repositories-meta/user2/repo1.git/objects/b2/60587271671842af0b036e4fe643c9d45b7ddd Binary files differnew file mode 100644 index 0000000000..9182ac0381 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/b2/60587271671842af0b036e4fe643c9d45b7ddd diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/d4/a1a6dcf7bd42891f264d484e80dac7e66b5410 b/tests/gitea-repositories-meta/user2/repo1.git/objects/d4/a1a6dcf7bd42891f264d484e80dac7e66b5410 Binary files differnew file mode 100644 index 0000000000..d7ef93c616 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/d4/a1a6dcf7bd42891f264d484e80dac7e66b5410 diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/d7/bd5b8cfb680f460e37b6fd7cf74c284e059118 b/tests/gitea-repositories-meta/user2/repo1.git/objects/d7/bd5b8cfb680f460e37b6fd7cf74c284e059118 Binary files differnew file mode 100644 index 0000000000..6039ff6619 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/d7/bd5b8cfb680f460e37b6fd7cf74c284e059118 diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/dc/7a8ba127fee870dd683310ce660dfe59333a1b b/tests/gitea-repositories-meta/user2/repo1.git/objects/dc/7a8ba127fee870dd683310ce660dfe59333a1b Binary files differnew file mode 100644 index 0000000000..7678d6754d --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/dc/7a8ba127fee870dd683310ce660dfe59333a1b diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/dd/59742c0f6672911f2b64cba5711ac00593ed32 b/tests/gitea-repositories-meta/user2/repo1.git/objects/dd/59742c0f6672911f2b64cba5711ac00593ed32 Binary files differnew file mode 100644 index 0000000000..f9137c5f77 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/dd/59742c0f6672911f2b64cba5711ac00593ed32 diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/info/packs b/tests/gitea-repositories-meta/user2/repo1.git/objects/info/packs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/objects/info/packs @@ -0,0 +1 @@ + diff --git a/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/DefaultBranch b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/DefaultBranch new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/DefaultBranch @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/branch2 b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/branch2 new file mode 100644 index 0000000000..5add7256cd --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/branch2 @@ -0,0 +1 @@ +985f0301dba5e7b34be866819cd15ad3d8f508ee diff --git a/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/develop b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/develop new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/develop @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/feature/1 b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/feature/1 new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/feature/1 @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/master b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/master new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/master @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/pr-to-update b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/pr-to-update new file mode 100644 index 0000000000..e0ee44dd14 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/pr-to-update @@ -0,0 +1 @@ +62fb502a7172d4453f0322a2cc85bddffa57f07a diff --git a/tests/gitea-repositories-meta/user2/repo1.git/refs/notes/commits b/tests/gitea-repositories-meta/user2/repo1.git/refs/notes/commits new file mode 100644 index 0000000000..6f837536fc --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/refs/notes/commits @@ -0,0 +1 @@ +3fa2f829675543ecfc16b2891aebe8bf0608a8f4 diff --git a/tests/gitea-repositories-meta/user2/repo1.git/refs/pull/2/head b/tests/gitea-repositories-meta/user2/repo1.git/refs/pull/2/head new file mode 100644 index 0000000000..98593d6537 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/refs/pull/2/head @@ -0,0 +1 @@ +4a357436d925b5c974181ff12a994538ddc5a269 diff --git a/tests/gitea-repositories-meta/user2/repo1.git/refs/pull/3/head b/tests/gitea-repositories-meta/user2/repo1.git/refs/pull/3/head new file mode 100644 index 0000000000..33a9eaa7f1 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/refs/pull/3/head @@ -0,0 +1 @@ +5f22f7d0d95d614d25a5b68592adb345a4b5c7fd diff --git a/tests/gitea-repositories-meta/user2/repo1.git/refs/pull/5/head b/tests/gitea-repositories-meta/user2/repo1.git/refs/pull/5/head new file mode 100644 index 0000000000..e0ee44dd14 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/refs/pull/5/head @@ -0,0 +1 @@ +62fb502a7172d4453f0322a2cc85bddffa57f07a diff --git a/tests/gitea-repositories-meta/user2/repo1.git/refs/tags/v1.1 b/tests/gitea-repositories-meta/user2/repo1.git/refs/tags/v1.1 new file mode 100644 index 0000000000..f98a263be6 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/refs/tags/v1.1 @@ -0,0 +1 @@ +65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/HEAD b/tests/gitea-repositories-meta/user2/repo1.wiki.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/config b/tests/gitea-repositories-meta/user2/repo1.wiki.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/description b/tests/gitea-repositories-meta/user2/repo1.wiki.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/info/exclude b/tests/gitea-repositories-meta/user2/repo1.wiki.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/0c/f15c3f66ec8384480ed9c3cf87c9e97fbb0ec3 b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/0c/f15c3f66ec8384480ed9c3cf87c9e97fbb0ec3 new file mode 100644 index 0000000000..c0314c5584 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/0c/f15c3f66ec8384480ed9c3cf87c9e97fbb0ec3 @@ -0,0 +1,2 @@ +xŽÑmÄ DóMÛÀY¬Í¢(ùJ©`5ÇÉœ-›K*Ki,Hi!?£Ñ<éiâVki0ZÿÔXH“D(Z6ĨGòSb» 3“JDÞhµó!÷uB¬ÌDaJp¡ íœÙèFôLƹ4+~´ëvÀ;‡£È +eýäžõç[Nx>KÝäÎü‡_så²q«/€]09MHpѤµêk¿Üä_dê-%¸í’‡Ûžï vÎ_¥]¡Ô^Õ/èI[t
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/0d/ca5bd9b5d7ef937710e056f575e86c0184ba85 b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/0d/ca5bd9b5d7ef937710e056f575e86c0184ba85 Binary files differnew file mode 100644 index 0000000000..a46c1925ec --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/0d/ca5bd9b5d7ef937710e056f575e86c0184ba85 diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/2c/54faec6c45d31c1abfaecdab471eac6633738a b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/2c/54faec6c45d31c1abfaecdab471eac6633738a Binary files differnew file mode 100644 index 0000000000..4cf6cdaa6f --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/2c/54faec6c45d31c1abfaecdab471eac6633738a diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/32/5dc4f8e9344e6668f21536a69d5f1d4ed53ca3 b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/32/5dc4f8e9344e6668f21536a69d5f1d4ed53ca3 Binary files differnew file mode 100644 index 0000000000..d52aa8e1ff --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/32/5dc4f8e9344e6668f21536a69d5f1d4ed53ca3 diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/42/3313fbd38093bb10d0c8387db9105409c6f196 b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/42/3313fbd38093bb10d0c8387db9105409c6f196 Binary files differnew file mode 100644 index 0000000000..bf4ae859f6 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/42/3313fbd38093bb10d0c8387db9105409c6f196 diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/71/911bf48766c7181518c1070911019fbb00b1fc b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/71/911bf48766c7181518c1070911019fbb00b1fc new file mode 100644 index 0000000000..84ade81980 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/71/911bf48766c7181518c1070911019fbb00b1fc @@ -0,0 +1 @@ +xÆM‚@†á¯MÛ àºré›°6ñœ&&&¬ü9LežÅ›w½Ý×åt<#ÞñÃÍ¡ªmv-·•0w¬b¦¢jyÌ–†¤Ú—~Ý‹[žæÉçý=HÄ÷.¾"à‚íµÄçÇ=
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/72/fc6251cc648e914c10009d31431fa2e38b9a20 b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/72/fc6251cc648e914c10009d31431fa2e38b9a20 Binary files differnew file mode 100644 index 0000000000..052fdf35a5 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/72/fc6251cc648e914c10009d31431fa2e38b9a20 diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/74/d5a0d73db9b9ef7aa9978eb7a099b08f54d45e b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/74/d5a0d73db9b9ef7aa9978eb7a099b08f54d45e Binary files differnew file mode 100644 index 0000000000..bcb0e0075c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/74/d5a0d73db9b9ef7aa9978eb7a099b08f54d45e diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/7c/d7c8fa852973c72c66eb120a6677c54a8697f7 b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/7c/d7c8fa852973c72c66eb120a6677c54a8697f7 Binary files differnew file mode 100644 index 0000000000..9c26495605 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/7c/d7c8fa852973c72c66eb120a6677c54a8697f7 diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/89/43a1d5f93c00439d5ffc0f8e36f5d60abae46c b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/89/43a1d5f93c00439d5ffc0f8e36f5d60abae46c Binary files differnew file mode 100644 index 0000000000..062641bee7 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/89/43a1d5f93c00439d5ffc0f8e36f5d60abae46c diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/c1/0d10b7e655b3dab1f53176db57c8219a5488d6 b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/c1/0d10b7e655b3dab1f53176db57c8219a5488d6 new file mode 100644 index 0000000000..8a6345dfa5 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/c1/0d10b7e655b3dab1f53176db57c8219a5488d6 @@ -0,0 +1,2 @@ +xŽÝmÃ0ƒû¬)nú±t2íSèçÓÙ`ņ¥¶“e‚,VY¡/Hâ#È[)¹EûÒ@NÈq¦è툎Ñr2«)DöÅ0âŒj§C®ìÑLŸœaCÓÃ&š4Bv]$Eßí²ðIÓ‘e…¯¼þP×r¿I…sÍe“zªË³~_ +åõÄ[yã‡è¢v£WíµV=í—›ü˘úH vZ~s»@݉%Á•Š¨?TÊZH
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/c4/b38c3e1395393f75bbbc2ed10c7eeb577d3b64 b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/c4/b38c3e1395393f75bbbc2ed10c7eeb577d3b64 Binary files differnew file mode 100644 index 0000000000..6dcfc96676 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/c4/b38c3e1395393f75bbbc2ed10c7eeb577d3b64 diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/e5/3d079e581fbfdea1075a54d5b621eab0090e52 b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/e5/3d079e581fbfdea1075a54d5b621eab0090e52 Binary files differnew file mode 100644 index 0000000000..ecdea7f694 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/e5/3d079e581fbfdea1075a54d5b621eab0090e52 diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/ea/82fc8777a24b07c26b3a4bf4e2742c03733eab b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/ea/82fc8777a24b07c26b3a4bf4e2742c03733eab Binary files differnew file mode 100644 index 0000000000..42a8258dbd --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/ea/82fc8777a24b07c26b3a4bf4e2742c03733eab diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/f5/05ec9b5c7a45a10259c1dda7f18434e5d55940 b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/f5/05ec9b5c7a45a10259c1dda7f18434e5d55940 Binary files differnew file mode 100644 index 0000000000..eaeadaeaee --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/objects/f5/05ec9b5c7a45a10259c1dda7f18434e5d55940 diff --git a/tests/gitea-repositories-meta/user2/repo1.wiki.git/refs/heads/master b/tests/gitea-repositories-meta/user2/repo1.wiki.git/refs/heads/master new file mode 100644 index 0000000000..38984b12b7 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.wiki.git/refs/heads/master @@ -0,0 +1 @@ +0dca5bd9b5d7ef937710e056f575e86c0184ba85 diff --git a/tests/gitea-repositories-meta/user2/repo15.git/HEAD b/tests/gitea-repositories-meta/user2/repo15.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo15.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/repo15.git/config b/tests/gitea-repositories-meta/user2/repo15.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo15.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/tests/gitea-repositories-meta/user2/repo15.git/description b/tests/gitea-repositories-meta/user2/repo15.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo15.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user2/repo15.git/hooks/post-receive b/tests/gitea-repositories-meta/user2/repo15.git/hooks/post-receive new file mode 100755 index 0000000000..4b3d452abc --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo15.git/hooks/post-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/post-receive.d"`; do + sh "$SHELL_FOLDER/post-receive.d/$i" +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo15.git/hooks/post-receive.d/gitea b/tests/gitea-repositories-meta/user2/repo15.git/hooks/post-receive.d/gitea new file mode 100755 index 0000000000..43a948da3a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo15.git/hooks/post-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive diff --git a/tests/gitea-repositories-meta/user2/repo15.git/hooks/pre-receive b/tests/gitea-repositories-meta/user2/repo15.git/hooks/pre-receive new file mode 100755 index 0000000000..4127013053 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo15.git/hooks/pre-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/pre-receive.d"`; do + sh "$SHELL_FOLDER/pre-receive.d/$i" +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo15.git/hooks/pre-receive.d/gitea b/tests/gitea-repositories-meta/user2/repo15.git/hooks/pre-receive.d/gitea new file mode 100755 index 0000000000..49d0940636 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo15.git/hooks/pre-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive diff --git a/tests/gitea-repositories-meta/user2/repo15.git/hooks/update b/tests/gitea-repositories-meta/user2/repo15.git/hooks/update new file mode 100755 index 0000000000..c186fe4a18 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo15.git/hooks/update @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/update.d"`; do + sh "$SHELL_FOLDER/update.d/$i" $1 $2 $3 +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo15.git/hooks/update.d/gitea b/tests/gitea-repositories-meta/user2/repo15.git/hooks/update.d/gitea new file mode 100755 index 0000000000..38101c2426 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo15.git/hooks/update.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3 diff --git a/tests/gitea-repositories-meta/user2/repo15.git/info/exclude b/tests/gitea-repositories-meta/user2/repo15.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo15.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user2/repo16.git/HEAD b/tests/gitea-repositories-meta/user2/repo16.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/repo16.git/config b/tests/gitea-repositories-meta/user2/repo16.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/tests/gitea-repositories-meta/user2/repo16.git/description b/tests/gitea-repositories-meta/user2/repo16.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user2/repo16.git/info/exclude b/tests/gitea-repositories-meta/user2/repo16.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user2/repo16.git/objects/0c/3d59dea27b97aa3cb66072745d7a2c51a7a8b1 b/tests/gitea-repositories-meta/user2/repo16.git/objects/0c/3d59dea27b97aa3cb66072745d7a2c51a7a8b1 Binary files differnew file mode 100644 index 0000000000..e62f09ac5f --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/objects/0c/3d59dea27b97aa3cb66072745d7a2c51a7a8b1 diff --git a/tests/gitea-repositories-meta/user2/repo16.git/objects/24/f83a471f77579fea57bac7255d6e64e70fce1c b/tests/gitea-repositories-meta/user2/repo16.git/objects/24/f83a471f77579fea57bac7255d6e64e70fce1c Binary files differnew file mode 100644 index 0000000000..2558be67ee --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/objects/24/f83a471f77579fea57bac7255d6e64e70fce1c diff --git a/tests/gitea-repositories-meta/user2/repo16.git/objects/27/566bd5738fc8b4e3fef3c5e72cce608537bd95 b/tests/gitea-repositories-meta/user2/repo16.git/objects/27/566bd5738fc8b4e3fef3c5e72cce608537bd95 Binary files differnew file mode 100644 index 0000000000..6042481f4e --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/objects/27/566bd5738fc8b4e3fef3c5e72cce608537bd95 diff --git a/tests/gitea-repositories-meta/user2/repo16.git/objects/3b/2b54fe3d9a8279d5b926124dccdf279b8eff2f b/tests/gitea-repositories-meta/user2/repo16.git/objects/3b/2b54fe3d9a8279d5b926124dccdf279b8eff2f new file mode 100644 index 0000000000..13de5951f1 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/objects/3b/2b54fe3d9a8279d5b926124dccdf279b8eff2f @@ -0,0 +1 @@ +x+)JMU06g040031Q(JMLÉMÕËMaXbR¶”10-&ªéCüÕþË’‘Ý=À,
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo16.git/objects/45/8121ce9a6b855c9733bae62093caf3f39685de b/tests/gitea-repositories-meta/user2/repo16.git/objects/45/8121ce9a6b855c9733bae62093caf3f39685de Binary files differnew file mode 100644 index 0000000000..7db2d33de1 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/objects/45/8121ce9a6b855c9733bae62093caf3f39685de diff --git a/tests/gitea-repositories-meta/user2/repo16.git/objects/50/99b81332712fe655e34e8dd63574f503f61811 b/tests/gitea-repositories-meta/user2/repo16.git/objects/50/99b81332712fe655e34e8dd63574f503f61811 new file mode 100644 index 0000000000..30997633f6 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/objects/50/99b81332712fe655e34e8dd63574f503f61811 @@ -0,0 +1,2 @@ +xQ +B!Eûvó„ŽO'!¢M´€Q‡×ƒÌðù ågÔúº‡çÞTKY:v½‰€N6»…‘b f›¢÷š&—‰19ÃÄÇhoýV\Wi§íyqyÞåj9ƒqõ„FØÒj´ãªË?ÙŸ¤æZ3¬Ëüßõ*S6#
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo16.git/objects/69/554a64c1e6030f051e5c3f94bfbd773cd6a324 b/tests/gitea-repositories-meta/user2/repo16.git/objects/69/554a64c1e6030f051e5c3f94bfbd773cd6a324 Binary files differnew file mode 100644 index 0000000000..77ea95d297 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/objects/69/554a64c1e6030f051e5c3f94bfbd773cd6a324 diff --git a/tests/gitea-repositories-meta/user2/repo16.git/objects/a4/3476a501516e065c5a82f05fd58fd319598bc1 b/tests/gitea-repositories-meta/user2/repo16.git/objects/a4/3476a501516e065c5a82f05fd58fd319598bc1 Binary files differnew file mode 100644 index 0000000000..e021b19eff --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/objects/a4/3476a501516e065c5a82f05fd58fd319598bc1 diff --git a/tests/gitea-repositories-meta/user2/repo16.git/objects/e9/4083fcdf1f10c545e9253a23c5e44a2ff68aac b/tests/gitea-repositories-meta/user2/repo16.git/objects/e9/4083fcdf1f10c545e9253a23c5e44a2ff68aac Binary files differnew file mode 100644 index 0000000000..780affc887 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/objects/e9/4083fcdf1f10c545e9253a23c5e44a2ff68aac diff --git a/tests/gitea-repositories-meta/user2/repo16.git/objects/f2/7c2b2b03dcab38beaf89b0ab4ff61f6de63441 b/tests/gitea-repositories-meta/user2/repo16.git/objects/f2/7c2b2b03dcab38beaf89b0ab4ff61f6de63441 Binary files differnew file mode 100644 index 0000000000..7cb8b91898 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/objects/f2/7c2b2b03dcab38beaf89b0ab4ff61f6de63441 diff --git a/tests/gitea-repositories-meta/user2/repo16.git/objects/f9/0451c72ef61a7645293d17b47be7a8e983da57 b/tests/gitea-repositories-meta/user2/repo16.git/objects/f9/0451c72ef61a7645293d17b47be7a8e983da57 Binary files differnew file mode 100644 index 0000000000..c7627ad36a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/objects/f9/0451c72ef61a7645293d17b47be7a8e983da57 diff --git a/tests/gitea-repositories-meta/user2/repo16.git/refs/heads/good-sign b/tests/gitea-repositories-meta/user2/repo16.git/refs/heads/good-sign new file mode 100644 index 0000000000..4750a76432 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/refs/heads/good-sign @@ -0,0 +1 @@ +f27c2b2b03dcab38beaf89b0ab4ff61f6de63441 diff --git a/tests/gitea-repositories-meta/user2/repo16.git/refs/heads/good-sign-not-yet-validated b/tests/gitea-repositories-meta/user2/repo16.git/refs/heads/good-sign-not-yet-validated new file mode 100644 index 0000000000..f68025f5b8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/refs/heads/good-sign-not-yet-validated @@ -0,0 +1 @@ +27566bd5738fc8b4e3fef3c5e72cce608537bd95 diff --git a/tests/gitea-repositories-meta/user2/repo16.git/refs/heads/master b/tests/gitea-repositories-meta/user2/repo16.git/refs/heads/master new file mode 100644 index 0000000000..65f9a9f714 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/refs/heads/master @@ -0,0 +1 @@ +69554a64c1e6030f051e5c3f94bfbd773cd6a324 diff --git a/tests/gitea-repositories-meta/user2/repo16.git/refs/heads/not-signed b/tests/gitea-repositories-meta/user2/repo16.git/refs/heads/not-signed new file mode 100644 index 0000000000..65f9a9f714 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo16.git/refs/heads/not-signed @@ -0,0 +1 @@ +69554a64c1e6030f051e5c3f94bfbd773cd6a324 diff --git a/tests/gitea-repositories-meta/user2/repo2.git/HEAD b/tests/gitea-repositories-meta/user2/repo2.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/repo2.git/config b/tests/gitea-repositories-meta/user2/repo2.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/tests/gitea-repositories-meta/user2/repo2.git/description b/tests/gitea-repositories-meta/user2/repo2.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user2/repo2.git/info/exclude b/tests/gitea-repositories-meta/user2/repo2.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user2/repo2.git/info/refs b/tests/gitea-repositories-meta/user2/repo2.git/info/refs new file mode 100644 index 0000000000..044e52e0f9 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/info/refs @@ -0,0 +1 @@ +205ac761f3326a7ebe416e8673760016450b5cec refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/0a/7d8b41ae9763e9a1743917396839d1791d49d0 b/tests/gitea-repositories-meta/user2/repo2.git/objects/0a/7d8b41ae9763e9a1743917396839d1791d49d0 Binary files differnew file mode 100644 index 0000000000..d62e3c623e --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/0a/7d8b41ae9763e9a1743917396839d1791d49d0 diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/0c/f15c3f66ec8384480ed9c3cf87c9e97fbb0ec3 b/tests/gitea-repositories-meta/user2/repo2.git/objects/0c/f15c3f66ec8384480ed9c3cf87c9e97fbb0ec3 new file mode 100644 index 0000000000..c0314c5584 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/0c/f15c3f66ec8384480ed9c3cf87c9e97fbb0ec3 @@ -0,0 +1,2 @@ +xŽÑmÄ DóMÛÀY¬Í¢(ùJ©`5ÇÉœ-›K*Ki,Hi!?£Ñ<éiâVki0ZÿÔXH“D(Z6ĨGòSb» 3“JDÞhµó!÷uB¬ÌDaJp¡ íœÙèFôLƹ4+~´ëvÀ;‡£È +eýäžõç[Nx>KÝäÎü‡_så²q«/€]09MHpѤµêk¿Üä_dê-%¸í’‡Ûžï vÎ_¥]¡Ô^Õ/èI[t
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/10/32bbf17fbc0d9c95bb5418dabe8f8c99278700 b/tests/gitea-repositories-meta/user2/repo2.git/objects/10/32bbf17fbc0d9c95bb5418dabe8f8c99278700 new file mode 100644 index 0000000000..736e40878e --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/10/32bbf17fbc0d9c95bb5418dabe8f8c99278700 @@ -0,0 +1,2 @@ +xŽK +Â0Eg™%Ÿ×":uä’ôŠ•¦J|‚îÞê¸çpËÖQ´ó~%
Ð9Ù„à‘G6GÎ ”ͦw(êžæE4}*íÙ{Ç)`YƆƒlŒeêMî—„JO¹Üš>Žµ¾õ©%ˆÞ^¿ÐÝ¿°L˜!]ÆN[v#E½6ÎU~/ÿúê0ZðîU'õgpJ5
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/1c/887eaa8d81fa86da7695d8f635cf17813eb422 b/tests/gitea-repositories-meta/user2/repo2.git/objects/1c/887eaa8d81fa86da7695d8f635cf17813eb422 new file mode 100644 index 0000000000..34fa593277 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/1c/887eaa8d81fa86da7695d8f635cf17813eb422 @@ -0,0 +1 @@ +x+)JMU07b040031QÈ*HM×Ë*Hg(œ(ý¥=í¸„¨ÄAvNAÆù»6þªÉÉÌKÕ+.KgHžºOþÝn9ŸÔjÿùÙ‹Ò³4l¸é
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/26/f842bcad37fa40a1bb34cbb5ee219ee35d863d b/tests/gitea-repositories-meta/user2/repo2.git/objects/26/f842bcad37fa40a1bb34cbb5ee219ee35d863d Binary files differnew file mode 100644 index 0000000000..c3e7e778c5 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/26/f842bcad37fa40a1bb34cbb5ee219ee35d863d diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/32/5dc4f8e9344e6668f21536a69d5f1d4ed53ca3 b/tests/gitea-repositories-meta/user2/repo2.git/objects/32/5dc4f8e9344e6668f21536a69d5f1d4ed53ca3 Binary files differnew file mode 100644 index 0000000000..d52aa8e1ff --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/32/5dc4f8e9344e6668f21536a69d5f1d4ed53ca3 diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/36/fff01c8c9f722d49d53186abd27b5be8d85338 b/tests/gitea-repositories-meta/user2/repo2.git/objects/36/fff01c8c9f722d49d53186abd27b5be8d85338 Binary files differnew file mode 100644 index 0000000000..fc0c8654b5 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/36/fff01c8c9f722d49d53186abd27b5be8d85338 diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/42/3313fbd38093bb10d0c8387db9105409c6f196 b/tests/gitea-repositories-meta/user2/repo2.git/objects/42/3313fbd38093bb10d0c8387db9105409c6f196 Binary files differnew file mode 100644 index 0000000000..bf4ae859f6 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/42/3313fbd38093bb10d0c8387db9105409c6f196 diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/71/911bf48766c7181518c1070911019fbb00b1fc b/tests/gitea-repositories-meta/user2/repo2.git/objects/71/911bf48766c7181518c1070911019fbb00b1fc new file mode 100644 index 0000000000..84ade81980 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/71/911bf48766c7181518c1070911019fbb00b1fc @@ -0,0 +1 @@ +xÆM‚@†á¯MÛ àºré›°6ñœ&&&¬ü9LežÅ›w½Ý×åt<#ÞñÃÍ¡ªmv-·•0w¬b¦¢jyÌ–†¤Ú—~Ý‹[žæÉçý=HÄ÷.¾"à‚íµÄçÇ=
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/72/fc6251cc648e914c10009d31431fa2e38b9a20 b/tests/gitea-repositories-meta/user2/repo2.git/objects/72/fc6251cc648e914c10009d31431fa2e38b9a20 Binary files differnew file mode 100644 index 0000000000..052fdf35a5 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/72/fc6251cc648e914c10009d31431fa2e38b9a20 diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/74/d5a0d73db9b9ef7aa9978eb7a099b08f54d45e b/tests/gitea-repositories-meta/user2/repo2.git/objects/74/d5a0d73db9b9ef7aa9978eb7a099b08f54d45e Binary files differnew file mode 100644 index 0000000000..bcb0e0075c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/74/d5a0d73db9b9ef7aa9978eb7a099b08f54d45e diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/7c/d7c8fa852973c72c66eb120a6677c54a8697f7 b/tests/gitea-repositories-meta/user2/repo2.git/objects/7c/d7c8fa852973c72c66eb120a6677c54a8697f7 Binary files differnew file mode 100644 index 0000000000..9c26495605 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/7c/d7c8fa852973c72c66eb120a6677c54a8697f7 diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/ba/1aed4e2ea2443d76cec241b96be4ec990852ec b/tests/gitea-repositories-meta/user2/repo2.git/objects/ba/1aed4e2ea2443d76cec241b96be4ec990852ec Binary files differnew file mode 100644 index 0000000000..add9a3af0d --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/ba/1aed4e2ea2443d76cec241b96be4ec990852ec diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/c1/0d10b7e655b3dab1f53176db57c8219a5488d6 b/tests/gitea-repositories-meta/user2/repo2.git/objects/c1/0d10b7e655b3dab1f53176db57c8219a5488d6 new file mode 100644 index 0000000000..8a6345dfa5 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/c1/0d10b7e655b3dab1f53176db57c8219a5488d6 @@ -0,0 +1,2 @@ +xŽÝmÃ0ƒû¬)nú±t2íSèçÓÙ`ņ¥¶“e‚,VY¡/Hâ#È[)¹EûÒ@NÈq¦è툎Ñr2«)DöÅ0âŒj§C®ìÑLŸœaCÓÃ&š4Bv]$Eßí²ðIÓ‘e…¯¼þP×r¿I…sÍe“zªË³~_ +åõÄ[yã‡è¢v£WíµV=í—›ü˘úH vZ~s»@݉%Á•Š¨?TÊZH
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/c4/b38c3e1395393f75bbbc2ed10c7eeb577d3b64 b/tests/gitea-repositories-meta/user2/repo2.git/objects/c4/b38c3e1395393f75bbbc2ed10c7eeb577d3b64 Binary files differnew file mode 100644 index 0000000000..6dcfc96676 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/c4/b38c3e1395393f75bbbc2ed10c7eeb577d3b64 diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/f5/05ec9b5c7a45a10259c1dda7f18434e5d55940 b/tests/gitea-repositories-meta/user2/repo2.git/objects/f5/05ec9b5c7a45a10259c1dda7f18434e5d55940 Binary files differnew file mode 100644 index 0000000000..eaeadaeaee --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/f5/05ec9b5c7a45a10259c1dda7f18434e5d55940 diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/info/commit-graph b/tests/gitea-repositories-meta/user2/repo2.git/objects/info/commit-graph Binary files differnew file mode 100644 index 0000000000..67dae50e83 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/info/commit-graph diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/info/packs b/tests/gitea-repositories-meta/user2/repo2.git/objects/info/packs new file mode 100644 index 0000000000..9eb91c8e0e --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/info/packs @@ -0,0 +1,2 @@ +P pack-a2f7ad943b3d857eb3ebdb4b35eeef38f63cf5d2.pack + diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/pack/pack-a2f7ad943b3d857eb3ebdb4b35eeef38f63cf5d2.bitmap b/tests/gitea-repositories-meta/user2/repo2.git/objects/pack/pack-a2f7ad943b3d857eb3ebdb4b35eeef38f63cf5d2.bitmap Binary files differnew file mode 100644 index 0000000000..8ecce324f4 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/pack/pack-a2f7ad943b3d857eb3ebdb4b35eeef38f63cf5d2.bitmap diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/pack/pack-a2f7ad943b3d857eb3ebdb4b35eeef38f63cf5d2.idx b/tests/gitea-repositories-meta/user2/repo2.git/objects/pack/pack-a2f7ad943b3d857eb3ebdb4b35eeef38f63cf5d2.idx Binary files differnew file mode 100644 index 0000000000..c4f319811e --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/pack/pack-a2f7ad943b3d857eb3ebdb4b35eeef38f63cf5d2.idx diff --git a/tests/gitea-repositories-meta/user2/repo2.git/objects/pack/pack-a2f7ad943b3d857eb3ebdb4b35eeef38f63cf5d2.pack b/tests/gitea-repositories-meta/user2/repo2.git/objects/pack/pack-a2f7ad943b3d857eb3ebdb4b35eeef38f63cf5d2.pack Binary files differnew file mode 100644 index 0000000000..9d10156ca7 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/objects/pack/pack-a2f7ad943b3d857eb3ebdb4b35eeef38f63cf5d2.pack diff --git a/tests/gitea-repositories-meta/user2/repo2.git/packed-refs b/tests/gitea-repositories-meta/user2/repo2.git/packed-refs new file mode 100644 index 0000000000..f785d91022 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled sorted +205ac761f3326a7ebe416e8673760016450b5cec refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/repo2.git/refs/heads/master b/tests/gitea-repositories-meta/user2/repo2.git/refs/heads/master new file mode 100644 index 0000000000..334d09ca02 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo2.git/refs/heads/master @@ -0,0 +1 @@ +1032bbf17fbc0d9c95bb5418dabe8f8c99278700 diff --git a/tests/gitea-repositories-meta/user2/repo20.git/HEAD b/tests/gitea-repositories-meta/user2/repo20.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/repo20.git/config b/tests/gitea-repositories-meta/user2/repo20.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/tests/gitea-repositories-meta/user2/repo20.git/description b/tests/gitea-repositories-meta/user2/repo20.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user2/repo20.git/hooks/post-receive b/tests/gitea-repositories-meta/user2/repo20.git/hooks/post-receive new file mode 100755 index 0000000000..f1f2709ddd --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/hooks/post-receive @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +data=$(cat) +exitcodes="" +hookname=$(basename $0) +GIT_DIR=${GIT_DIR:-$(dirname $0)} + +for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do +test -x "${hook}" && test -f "${hook}" || continue +echo "${data}" | "${hook}" +exitcodes="${exitcodes} $?" +done + +for i in ${exitcodes}; do +[ ${i} -eq 0 ] || exit ${i} +done diff --git a/tests/gitea-repositories-meta/user2/repo20.git/hooks/post-receive.d/gitea b/tests/gitea-repositories-meta/user2/repo20.git/hooks/post-receive.d/gitea new file mode 100755 index 0000000000..ee2ab2f2df --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/hooks/post-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"/home/tris/Projects/go/src/code.gitea.io/gitea/gitea" hook --config='/home/tris/Projects/go/src/code.gitea.io/gitea/custom/conf/app.ini' post-receive diff --git a/tests/gitea-repositories-meta/user2/repo20.git/hooks/pre-receive b/tests/gitea-repositories-meta/user2/repo20.git/hooks/pre-receive new file mode 100755 index 0000000000..f1f2709ddd --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/hooks/pre-receive @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +data=$(cat) +exitcodes="" +hookname=$(basename $0) +GIT_DIR=${GIT_DIR:-$(dirname $0)} + +for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do +test -x "${hook}" && test -f "${hook}" || continue +echo "${data}" | "${hook}" +exitcodes="${exitcodes} $?" +done + +for i in ${exitcodes}; do +[ ${i} -eq 0 ] || exit ${i} +done diff --git a/tests/gitea-repositories-meta/user2/repo20.git/hooks/pre-receive.d/gitea b/tests/gitea-repositories-meta/user2/repo20.git/hooks/pre-receive.d/gitea new file mode 100755 index 0000000000..cdbc3c7c1a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/hooks/pre-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"/home/tris/Projects/go/src/code.gitea.io/gitea/gitea" hook --config='/home/tris/Projects/go/src/code.gitea.io/gitea/custom/conf/app.ini' pre-receive diff --git a/tests/gitea-repositories-meta/user2/repo20.git/hooks/update b/tests/gitea-repositories-meta/user2/repo20.git/hooks/update new file mode 100755 index 0000000000..df5bd27f10 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/hooks/update @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +exitcodes="" +hookname=$(basename $0) +GIT_DIR=${GIT_DIR:-$(dirname $0)} + +for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do +test -x "${hook}" && test -f "${hook}" || continue +"${hook}" $1 $2 $3 +exitcodes="${exitcodes} $?" +done + +for i in ${exitcodes}; do +[ ${i} -eq 0 ] || exit ${i} +done diff --git a/tests/gitea-repositories-meta/user2/repo20.git/hooks/update.d/gitea b/tests/gitea-repositories-meta/user2/repo20.git/hooks/update.d/gitea new file mode 100755 index 0000000000..7447b2fe01 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/hooks/update.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"/home/tris/Projects/go/src/code.gitea.io/gitea/gitea" hook --config='/home/tris/Projects/go/src/code.gitea.io/gitea/custom/conf/app.ini' update $1 $2 $3 diff --git a/tests/gitea-repositories-meta/user2/repo20.git/info/exclude b/tests/gitea-repositories-meta/user2/repo20.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user2/repo20.git/info/refs b/tests/gitea-repositories-meta/user2/repo20.git/info/refs new file mode 100644 index 0000000000..6d83c82bcf --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/info/refs @@ -0,0 +1 @@ +808038d2f71b0ab020991439cffd24309c7bc530 refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/repo20.git/objects/02/15cbe13d2695a2c3464ab5e59f47f37c3ff5d5 b/tests/gitea-repositories-meta/user2/repo20.git/objects/02/15cbe13d2695a2c3464ab5e59f47f37c3ff5d5 Binary files differnew file mode 100644 index 0000000000..17868e9d33 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/objects/02/15cbe13d2695a2c3464ab5e59f47f37c3ff5d5 diff --git a/tests/gitea-repositories-meta/user2/repo20.git/objects/05/81d7edf45206787ff93956ea892e8a2ae77604 b/tests/gitea-repositories-meta/user2/repo20.git/objects/05/81d7edf45206787ff93956ea892e8a2ae77604 Binary files differnew file mode 100644 index 0000000000..10ab94aa99 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/objects/05/81d7edf45206787ff93956ea892e8a2ae77604 diff --git a/tests/gitea-repositories-meta/user2/repo20.git/objects/1b/271d83842d348b1ee71d8e6ead400aaeb4d1b5 b/tests/gitea-repositories-meta/user2/repo20.git/objects/1b/271d83842d348b1ee71d8e6ead400aaeb4d1b5 Binary files differnew file mode 100644 index 0000000000..01b07ff6e7 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/objects/1b/271d83842d348b1ee71d8e6ead400aaeb4d1b5 diff --git a/tests/gitea-repositories-meta/user2/repo20.git/objects/29/5ba6ac57fdd46f62a51272f40e60b6dea697b2 b/tests/gitea-repositories-meta/user2/repo20.git/objects/29/5ba6ac57fdd46f62a51272f40e60b6dea697b2 Binary files differnew file mode 100644 index 0000000000..8a24f2e7b2 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/objects/29/5ba6ac57fdd46f62a51272f40e60b6dea697b2 diff --git a/tests/gitea-repositories-meta/user2/repo20.git/objects/2c/ec0f7069ed09d934e904c49f414d8bdf818ce4 b/tests/gitea-repositories-meta/user2/repo20.git/objects/2c/ec0f7069ed09d934e904c49f414d8bdf818ce4 Binary files differnew file mode 100644 index 0000000000..c113af8cde --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/objects/2c/ec0f7069ed09d934e904c49f414d8bdf818ce4 diff --git a/tests/gitea-repositories-meta/user2/repo20.git/objects/41/4a282859758ba7b159bfbd9c2b193eb8f135ee b/tests/gitea-repositories-meta/user2/repo20.git/objects/41/4a282859758ba7b159bfbd9c2b193eb8f135ee Binary files differnew file mode 100644 index 0000000000..c6fb0cfe68 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/objects/41/4a282859758ba7b159bfbd9c2b193eb8f135ee diff --git a/tests/gitea-repositories-meta/user2/repo20.git/objects/80/8038d2f71b0ab020991439cffd24309c7bc530 b/tests/gitea-repositories-meta/user2/repo20.git/objects/80/8038d2f71b0ab020991439cffd24309c7bc530 Binary files differnew file mode 100644 index 0000000000..21147c874c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/objects/80/8038d2f71b0ab020991439cffd24309c7bc530 diff --git a/tests/gitea-repositories-meta/user2/repo20.git/objects/83/70977f63979e140b6b58992b1fdb4098b24cd9 b/tests/gitea-repositories-meta/user2/repo20.git/objects/83/70977f63979e140b6b58992b1fdb4098b24cd9 Binary files differnew file mode 100644 index 0000000000..3a20da80ba --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/objects/83/70977f63979e140b6b58992b1fdb4098b24cd9 diff --git a/tests/gitea-repositories-meta/user2/repo20.git/objects/8c/e1dee41e1a3700819a9a309f275f8dc7b7e0b6 b/tests/gitea-repositories-meta/user2/repo20.git/objects/8c/e1dee41e1a3700819a9a309f275f8dc7b7e0b6 Binary files differnew file mode 100644 index 0000000000..fa58c0319b --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/objects/8c/e1dee41e1a3700819a9a309f275f8dc7b7e0b6 diff --git a/tests/gitea-repositories-meta/user2/repo20.git/objects/ba/3aeafe10402c6b29535a58d91def7e43638d9d b/tests/gitea-repositories-meta/user2/repo20.git/objects/ba/3aeafe10402c6b29535a58d91def7e43638d9d Binary files differnew file mode 100644 index 0000000000..eeb034d2f4 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/objects/ba/3aeafe10402c6b29535a58d91def7e43638d9d diff --git a/tests/gitea-repositories-meta/user2/repo20.git/objects/c5/0ac6b9e25abb8200bb377755367d7265c581cf b/tests/gitea-repositories-meta/user2/repo20.git/objects/c5/0ac6b9e25abb8200bb377755367d7265c581cf Binary files differnew file mode 100644 index 0000000000..7b03dcc620 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/objects/c5/0ac6b9e25abb8200bb377755367d7265c581cf diff --git a/tests/gitea-repositories-meta/user2/repo20.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a b/tests/gitea-repositories-meta/user2/repo20.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a Binary files differnew file mode 100644 index 0000000000..6802d49492 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a diff --git a/tests/gitea-repositories-meta/user2/repo20.git/objects/db/89c972fc57862eae378f45b74aca228037d415 b/tests/gitea-repositories-meta/user2/repo20.git/objects/db/89c972fc57862eae378f45b74aca228037d415 Binary files differnew file mode 100644 index 0000000000..c627859fd0 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/objects/db/89c972fc57862eae378f45b74aca228037d415 diff --git a/tests/gitea-repositories-meta/user2/repo20.git/objects/info/packs b/tests/gitea-repositories-meta/user2/repo20.git/objects/info/packs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/objects/info/packs @@ -0,0 +1 @@ + diff --git a/tests/gitea-repositories-meta/user2/repo20.git/refs/heads/master b/tests/gitea-repositories-meta/user2/repo20.git/refs/heads/master new file mode 100644 index 0000000000..66b845cd38 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo20.git/refs/heads/master @@ -0,0 +1 @@ +808038d2f71b0ab020991439cffd24309c7bc530 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/HEAD b/tests/gitea-repositories-meta/user2/utf8.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/utf8.git/config b/tests/gitea-repositories-meta/user2/utf8.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/tests/gitea-repositories-meta/user2/utf8.git/description b/tests/gitea-repositories-meta/user2/utf8.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user2/utf8.git/hooks/post-receive b/tests/gitea-repositories-meta/user2/utf8.git/hooks/post-receive new file mode 100755 index 0000000000..4b3d452abc --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/hooks/post-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/post-receive.d"`; do + sh "$SHELL_FOLDER/post-receive.d/$i" +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/utf8.git/hooks/post-receive.d/gitea b/tests/gitea-repositories-meta/user2/utf8.git/hooks/post-receive.d/gitea new file mode 100755 index 0000000000..43a948da3a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/hooks/post-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive diff --git a/tests/gitea-repositories-meta/user2/utf8.git/hooks/pre-receive b/tests/gitea-repositories-meta/user2/utf8.git/hooks/pre-receive new file mode 100755 index 0000000000..4127013053 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/hooks/pre-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/pre-receive.d"`; do + sh "$SHELL_FOLDER/pre-receive.d/$i" +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/utf8.git/hooks/pre-receive.d/gitea b/tests/gitea-repositories-meta/user2/utf8.git/hooks/pre-receive.d/gitea new file mode 100755 index 0000000000..49d0940636 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/hooks/pre-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive diff --git a/tests/gitea-repositories-meta/user2/utf8.git/hooks/update b/tests/gitea-repositories-meta/user2/utf8.git/hooks/update new file mode 100755 index 0000000000..c186fe4a18 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/hooks/update @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/update.d"`; do + sh "$SHELL_FOLDER/update.d/$i" $1 $2 $3 +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/utf8.git/hooks/update.d/gitea b/tests/gitea-repositories-meta/user2/utf8.git/hooks/update.d/gitea new file mode 100755 index 0000000000..38101c2426 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/hooks/update.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/info/exclude b/tests/gitea-repositories-meta/user2/utf8.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user2/utf8.git/info/refs b/tests/gitea-repositories-meta/user2/utf8.git/info/refs new file mode 100644 index 0000000000..29eaf9f16c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/info/refs @@ -0,0 +1,9 @@ +ebf146f803fccbc1471ef01d8fa0fe12c14e61a5 refs/heads/Grüßen +3a810dbf6b96afaa8c5f69a8b6ec1dabfca7368b refs/heads/Plus+Is+Not+Space +3aa73c3499bff049a352b4e265575373e964b89a refs/heads/master +ebf146f803fccbc1471ef01d8fa0fe12c14e61a5 refs/heads/ГлавнаÑВетка +ebf146f803fccbc1471ef01d8fa0fe12c14e61a5 refs/heads/а/б/в +28d579e4920fbf4f66e71dab3e779d9fbf41422a refs/heads/ブランム+ebf146f803fccbc1471ef01d8fa0fe12c14e61a5 refs/tags/Ð/人 +ebf146f803fccbc1471ef01d8fa0fe12c14e61a5 refs/tags/ТÑг +28d579e4920fbf4f66e71dab3e779d9fbf41422a refs/tags/ã‚¿ã‚° diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/14/c42687126acae9d1ad41d7bdb528f811065a6a b/tests/gitea-repositories-meta/user2/utf8.git/objects/14/c42687126acae9d1ad41d7bdb528f811065a6a Binary files differnew file mode 100644 index 0000000000..19fdbf1232 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/14/c42687126acae9d1ad41d7bdb528f811065a6a diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/1d/5e00f305a7ca6a8a94e65456820a6d260adab8 b/tests/gitea-repositories-meta/user2/utf8.git/objects/1d/5e00f305a7ca6a8a94e65456820a6d260adab8 Binary files differnew file mode 100644 index 0000000000..684b457c3e --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/1d/5e00f305a7ca6a8a94e65456820a6d260adab8 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/28/d579e4920fbf4f66e71dab3e779d9fbf41422a b/tests/gitea-repositories-meta/user2/utf8.git/objects/28/d579e4920fbf4f66e71dab3e779d9fbf41422a new file mode 100644 index 0000000000..413ef4cea0 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/28/d579e4920fbf4f66e71dab3e779d9fbf41422a @@ -0,0 +1,3 @@ +x•ŽAjÄ0E»ö)´œR(²ci<PJ{Å–˜)ILo_ÓM×]}xü/×u½uDO½©‚/¤ˆ6!É9K’KT¦Hœ +—À(EæäviºuÐÙ|dK8YÎsöñìÕЗd‚¦>¢ì…œÜûµ6¸Ú¼ý·>dÝ}Íu}OñÄ)xÁ€èi]ÿ%¹ÏRàKvÙôP°Û¢Ð똛lù +u[¾ád£§ëÑÇ£>»²´QÑ
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/3a/810dbf6b96afaa8c5f69a8b6ec1dabfca7368b b/tests/gitea-repositories-meta/user2/utf8.git/objects/3a/810dbf6b96afaa8c5f69a8b6ec1dabfca7368b Binary files differnew file mode 100644 index 0000000000..4f6634b5f7 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/3a/810dbf6b96afaa8c5f69a8b6ec1dabfca7368b diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/3a/a73c3499bff049a352b4e265575373e964b89a b/tests/gitea-repositories-meta/user2/utf8.git/objects/3a/a73c3499bff049a352b4e265575373e964b89a Binary files differnew file mode 100644 index 0000000000..0fcdfdfe72 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/3a/a73c3499bff049a352b4e265575373e964b89a diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/3a/c6084110205f98174c4f1ec7e78cb21a15dfc2 b/tests/gitea-repositories-meta/user2/utf8.git/objects/3a/c6084110205f98174c4f1ec7e78cb21a15dfc2 Binary files differnew file mode 100644 index 0000000000..6d9e6b3758 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/3a/c6084110205f98174c4f1ec7e78cb21a15dfc2 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/4c/61dd0a799e0830e77edfe6c74f7c349bc8e62a b/tests/gitea-repositories-meta/user2/utf8.git/objects/4c/61dd0a799e0830e77edfe6c74f7c349bc8e62a Binary files differnew file mode 100644 index 0000000000..17b3104773 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/4c/61dd0a799e0830e77edfe6c74f7c349bc8e62a diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/50/4d9fe743979d4e9785a25a363c7007293f0838 b/tests/gitea-repositories-meta/user2/utf8.git/objects/50/4d9fe743979d4e9785a25a363c7007293f0838 Binary files differnew file mode 100644 index 0000000000..25794ae805 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/50/4d9fe743979d4e9785a25a363c7007293f0838 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/56/92bcf9f7c9eacb1ad68442161f2573877f96f4 b/tests/gitea-repositories-meta/user2/utf8.git/objects/56/92bcf9f7c9eacb1ad68442161f2573877f96f4 Binary files differnew file mode 100644 index 0000000000..36c0db1ece --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/56/92bcf9f7c9eacb1ad68442161f2573877f96f4 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/59/e2c41e8f5140bb0182acebec17c8ad9831cc62 b/tests/gitea-repositories-meta/user2/utf8.git/objects/59/e2c41e8f5140bb0182acebec17c8ad9831cc62 Binary files differnew file mode 100644 index 0000000000..736a24227c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/59/e2c41e8f5140bb0182acebec17c8ad9831cc62 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/64/89894ad11093fdc49c0ed857d80682344a7264 b/tests/gitea-repositories-meta/user2/utf8.git/objects/64/89894ad11093fdc49c0ed857d80682344a7264 Binary files differnew file mode 100644 index 0000000000..87e198aa9c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/64/89894ad11093fdc49c0ed857d80682344a7264 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/6d/0c79ce3401c67d1ad522e61c47083a9fdee16c b/tests/gitea-repositories-meta/user2/utf8.git/objects/6d/0c79ce3401c67d1ad522e61c47083a9fdee16c Binary files differnew file mode 100644 index 0000000000..dab81f8d11 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/6d/0c79ce3401c67d1ad522e61c47083a9fdee16c diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/84/7c6d93c6860dd377651245711b7fbcd34a18d4 b/tests/gitea-repositories-meta/user2/utf8.git/objects/84/7c6d93c6860dd377651245711b7fbcd34a18d4 Binary files differnew file mode 100644 index 0000000000..ffea321c19 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/84/7c6d93c6860dd377651245711b7fbcd34a18d4 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/9b/9cc8f558d1c4f815592496fa24308ba2a9c824 b/tests/gitea-repositories-meta/user2/utf8.git/objects/9b/9cc8f558d1c4f815592496fa24308ba2a9c824 Binary files differnew file mode 100644 index 0000000000..8f033d5ae7 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/9b/9cc8f558d1c4f815592496fa24308ba2a9c824 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/a4/f1bb3f2f8c6a0e840e935812ef4903ce515dad b/tests/gitea-repositories-meta/user2/utf8.git/objects/a4/f1bb3f2f8c6a0e840e935812ef4903ce515dad Binary files differnew file mode 100644 index 0000000000..9655a74c83 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/a4/f1bb3f2f8c6a0e840e935812ef4903ce515dad diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/a9/a61830fbf4e84999d3b20cf178954366701fe5 b/tests/gitea-repositories-meta/user2/utf8.git/objects/a9/a61830fbf4e84999d3b20cf178954366701fe5 Binary files differnew file mode 100644 index 0000000000..a2ceb0044a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/a9/a61830fbf4e84999d3b20cf178954366701fe5 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/c7/85b65bf16928b58567cb23669125c0ccd25a4f b/tests/gitea-repositories-meta/user2/utf8.git/objects/c7/85b65bf16928b58567cb23669125c0ccd25a4f Binary files differnew file mode 100644 index 0000000000..2cc606b7f2 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/c7/85b65bf16928b58567cb23669125c0ccd25a4f diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/e9/63733b8a355cf860c465b4af7b236a6ef08783 b/tests/gitea-repositories-meta/user2/utf8.git/objects/e9/63733b8a355cf860c465b4af7b236a6ef08783 Binary files differnew file mode 100644 index 0000000000..8d16f34e59 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/e9/63733b8a355cf860c465b4af7b236a6ef08783 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/eb/f146f803fccbc1471ef01d8fa0fe12c14e61a5 b/tests/gitea-repositories-meta/user2/utf8.git/objects/eb/f146f803fccbc1471ef01d8fa0fe12c14e61a5 new file mode 100644 index 0000000000..eec8265be4 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/eb/f146f803fccbc1471ef01d8fa0fe12c14e61a5 @@ -0,0 +1 @@ +x•Ž]jC!…ûì*æ½PtFÇ+”Ò.e4#®¹Ášì¾¶;èÓÃùùÊÑ{›€ž_æPÕÄ—«OXÙÓær±bÚ2Š+Åyg®2ô2D"ò)õI(`öŠBiÍù¼%1rŸçcÀý¦áýO>õ!ýºë[9ú¸@‰-!¼Z´Ö,w¡MýWÉ|NPÛ®ðÝæÊs´}o.Òæñûrƒ¡UQóˆM‡
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/ee/9686cb562f492f64381bff7f298b2a1c67a141 b/tests/gitea-repositories-meta/user2/utf8.git/objects/ee/9686cb562f492f64381bff7f298b2a1c67a141 Binary files differnew file mode 100644 index 0000000000..013c499793 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/ee/9686cb562f492f64381bff7f298b2a1c67a141 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/f4/02ff67c0b3161c3988dbf6188e6e0df257fd75 b/tests/gitea-repositories-meta/user2/utf8.git/objects/f4/02ff67c0b3161c3988dbf6188e6e0df257fd75 Binary files differnew file mode 100644 index 0000000000..4ce3cc4136 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/f4/02ff67c0b3161c3988dbf6188e6e0df257fd75 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/objects/info/packs b/tests/gitea-repositories-meta/user2/utf8.git/objects/info/packs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/objects/info/packs @@ -0,0 +1 @@ + diff --git a/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/Grüßen b/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/Grüßen new file mode 100644 index 0000000000..abd33644f8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/Grüßen @@ -0,0 +1 @@ +ebf146f803fccbc1471ef01d8fa0fe12c14e61a5 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/Plus+Is+Not+Space b/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/Plus+Is+Not+Space new file mode 100644 index 0000000000..c2850d4c4d --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/Plus+Is+Not+Space @@ -0,0 +1 @@ +59e2c41e8f5140bb0182acebec17c8ad9831cc62 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/master b/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/master new file mode 100644 index 0000000000..560458b5aa --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/master @@ -0,0 +1 @@ +3aa73c3499bff049a352b4e265575373e964b89a diff --git a/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/ГлавнаÑВетка b/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/ГлавнаÑВетка new file mode 100644 index 0000000000..abd33644f8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/ГлавнаÑВетка @@ -0,0 +1 @@ +ebf146f803fccbc1471ef01d8fa0fe12c14e61a5 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/а/б/в b/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/а/б/в new file mode 100644 index 0000000000..abd33644f8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/а/б/в @@ -0,0 +1 @@ +ebf146f803fccbc1471ef01d8fa0fe12c14e61a5 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/ブランムb/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/ブランムnew file mode 100644 index 0000000000..b0935a9121 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/refs/heads/ブランム@@ -0,0 +1 @@ +28d579e4920fbf4f66e71dab3e779d9fbf41422a diff --git a/tests/gitea-repositories-meta/user2/utf8.git/refs/tags/Ð/人 b/tests/gitea-repositories-meta/user2/utf8.git/refs/tags/Ð/人 new file mode 100644 index 0000000000..abd33644f8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/refs/tags/Ð/人 @@ -0,0 +1 @@ +ebf146f803fccbc1471ef01d8fa0fe12c14e61a5 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/refs/tags/ТÑг b/tests/gitea-repositories-meta/user2/utf8.git/refs/tags/ТÑг new file mode 100644 index 0000000000..abd33644f8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/refs/tags/ТÑг @@ -0,0 +1 @@ +ebf146f803fccbc1471ef01d8fa0fe12c14e61a5 diff --git a/tests/gitea-repositories-meta/user2/utf8.git/refs/tags/ã‚¿ã‚° b/tests/gitea-repositories-meta/user2/utf8.git/refs/tags/ã‚¿ã‚° new file mode 100644 index 0000000000..b0935a9121 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/utf8.git/refs/tags/ã‚¿ã‚° @@ -0,0 +1 @@ +28d579e4920fbf4f66e71dab3e779d9fbf41422a diff --git a/tests/gitea-repositories-meta/user27/repo49.git/HEAD b/tests/gitea-repositories-meta/user27/repo49.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user27/repo49.git/config b/tests/gitea-repositories-meta/user27/repo49.git/config new file mode 100644 index 0000000000..64280b806c --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = true + symlinks = false + ignorecase = true diff --git a/tests/gitea-repositories-meta/user27/repo49.git/description b/tests/gitea-repositories-meta/user27/repo49.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user27/repo49.git/hooks/post-receive b/tests/gitea-repositories-meta/user27/repo49.git/hooks/post-receive new file mode 100644 index 0000000000..f1f2709ddd --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/hooks/post-receive @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +data=$(cat) +exitcodes="" +hookname=$(basename $0) +GIT_DIR=${GIT_DIR:-$(dirname $0)} + +for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do +test -x "${hook}" && test -f "${hook}" || continue +echo "${data}" | "${hook}" +exitcodes="${exitcodes} $?" +done + +for i in ${exitcodes}; do +[ ${i} -eq 0 ] || exit ${i} +done diff --git a/tests/gitea-repositories-meta/user27/repo49.git/hooks/post-receive.d/gitea b/tests/gitea-repositories-meta/user27/repo49.git/hooks/post-receive.d/gitea new file mode 100644 index 0000000000..43a948da3a --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/hooks/post-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive diff --git a/tests/gitea-repositories-meta/user27/repo49.git/hooks/pre-receive b/tests/gitea-repositories-meta/user27/repo49.git/hooks/pre-receive new file mode 100644 index 0000000000..f1f2709ddd --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/hooks/pre-receive @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +data=$(cat) +exitcodes="" +hookname=$(basename $0) +GIT_DIR=${GIT_DIR:-$(dirname $0)} + +for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do +test -x "${hook}" && test -f "${hook}" || continue +echo "${data}" | "${hook}" +exitcodes="${exitcodes} $?" +done + +for i in ${exitcodes}; do +[ ${i} -eq 0 ] || exit ${i} +done diff --git a/tests/gitea-repositories-meta/user27/repo49.git/hooks/pre-receive.d/gitea b/tests/gitea-repositories-meta/user27/repo49.git/hooks/pre-receive.d/gitea new file mode 100644 index 0000000000..49d0940636 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/hooks/pre-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive diff --git a/tests/gitea-repositories-meta/user27/repo49.git/hooks/update b/tests/gitea-repositories-meta/user27/repo49.git/hooks/update new file mode 100644 index 0000000000..df5bd27f10 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/hooks/update @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +exitcodes="" +hookname=$(basename $0) +GIT_DIR=${GIT_DIR:-$(dirname $0)} + +for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do +test -x "${hook}" && test -f "${hook}" || continue +"${hook}" $1 $2 $3 +exitcodes="${exitcodes} $?" +done + +for i in ${exitcodes}; do +[ ${i} -eq 0 ] || exit ${i} +done diff --git a/tests/gitea-repositories-meta/user27/repo49.git/hooks/update.d/gitea b/tests/gitea-repositories-meta/user27/repo49.git/hooks/update.d/gitea new file mode 100644 index 0000000000..38101c2426 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/hooks/update.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3 diff --git a/tests/gitea-repositories-meta/user27/repo49.git/info/exclude b/tests/gitea-repositories-meta/user27/repo49.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user27/repo49.git/info/refs b/tests/gitea-repositories-meta/user27/repo49.git/info/refs new file mode 100644 index 0000000000..22f08279c0 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/info/refs @@ -0,0 +1 @@ +aacbdfe9e1c4b47f60abe81849045fa4e96f1d75 refs/heads/master diff --git a/tests/gitea-repositories-meta/user27/repo49.git/objects/47/34b1f84a367fa1b81c31aa4234a5bad11cafa3 b/tests/gitea-repositories-meta/user27/repo49.git/objects/47/34b1f84a367fa1b81c31aa4234a5bad11cafa3 Binary files differnew file mode 100644 index 0000000000..b6f121a4bb --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/objects/47/34b1f84a367fa1b81c31aa4234a5bad11cafa3 diff --git a/tests/gitea-repositories-meta/user27/repo49.git/objects/4d/31f3a12656368a8d9180f431d40d0fc408be2d b/tests/gitea-repositories-meta/user27/repo49.git/objects/4d/31f3a12656368a8d9180f431d40d0fc408be2d Binary files differnew file mode 100644 index 0000000000..d2f4c1d04e --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/objects/4d/31f3a12656368a8d9180f431d40d0fc408be2d diff --git a/tests/gitea-repositories-meta/user27/repo49.git/objects/51/f84af231345367fd5d61ceb89efb3b6d757061 b/tests/gitea-repositories-meta/user27/repo49.git/objects/51/f84af231345367fd5d61ceb89efb3b6d757061 Binary files differnew file mode 100644 index 0000000000..aa34a8a767 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/objects/51/f84af231345367fd5d61ceb89efb3b6d757061 diff --git a/tests/gitea-repositories-meta/user27/repo49.git/objects/79/3aa682b06ae032641abf70c5dfeade28c07c52 b/tests/gitea-repositories-meta/user27/repo49.git/objects/79/3aa682b06ae032641abf70c5dfeade28c07c52 Binary files differnew file mode 100644 index 0000000000..3f9705fef4 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/objects/79/3aa682b06ae032641abf70c5dfeade28c07c52 diff --git a/tests/gitea-repositories-meta/user27/repo49.git/objects/aa/cbdfe9e1c4b47f60abe81849045fa4e96f1d75 b/tests/gitea-repositories-meta/user27/repo49.git/objects/aa/cbdfe9e1c4b47f60abe81849045fa4e96f1d75 Binary files differnew file mode 100644 index 0000000000..74419f4b47 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/objects/aa/cbdfe9e1c4b47f60abe81849045fa4e96f1d75 diff --git a/tests/gitea-repositories-meta/user27/repo49.git/objects/dd/392e939ea4936b2459219c9c9a1f25547ccaeb b/tests/gitea-repositories-meta/user27/repo49.git/objects/dd/392e939ea4936b2459219c9c9a1f25547ccaeb Binary files differnew file mode 100644 index 0000000000..844eb1c2a6 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/objects/dd/392e939ea4936b2459219c9c9a1f25547ccaeb diff --git a/tests/gitea-repositories-meta/user27/repo49.git/objects/f2/8eeca3df7614fd4f10c1030f13feb418ef3c6f b/tests/gitea-repositories-meta/user27/repo49.git/objects/f2/8eeca3df7614fd4f10c1030f13feb418ef3c6f Binary files differnew file mode 100644 index 0000000000..0699bff833 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/objects/f2/8eeca3df7614fd4f10c1030f13feb418ef3c6f diff --git a/tests/gitea-repositories-meta/user27/repo49.git/objects/info/packs b/tests/gitea-repositories-meta/user27/repo49.git/objects/info/packs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/objects/info/packs @@ -0,0 +1 @@ + diff --git a/tests/gitea-repositories-meta/user27/repo49.git/refs/heads/master b/tests/gitea-repositories-meta/user27/repo49.git/refs/heads/master new file mode 100644 index 0000000000..0f13243bfd --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/refs/heads/master @@ -0,0 +1 @@ +aacbdfe9e1c4b47f60abe81849045fa4e96f1d75 diff --git a/tests/gitea-repositories-meta/user27/repo49.git/refs/heads/test/archive b/tests/gitea-repositories-meta/user27/repo49.git/refs/heads/test/archive new file mode 100644 index 0000000000..0f13243bfd --- /dev/null +++ b/tests/gitea-repositories-meta/user27/repo49.git/refs/heads/test/archive @@ -0,0 +1 @@ +aacbdfe9e1c4b47f60abe81849045fa4e96f1d75 diff --git a/tests/gitea-repositories-meta/user27/template1.git/HEAD b/tests/gitea-repositories-meta/user27/template1.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user27/template1.git/config b/tests/gitea-repositories-meta/user27/template1.git/config new file mode 100644 index 0000000000..64280b806c --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = true + symlinks = false + ignorecase = true diff --git a/tests/gitea-repositories-meta/user27/template1.git/description b/tests/gitea-repositories-meta/user27/template1.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user27/template1.git/hooks/post-receive b/tests/gitea-repositories-meta/user27/template1.git/hooks/post-receive new file mode 100644 index 0000000000..f1f2709ddd --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/hooks/post-receive @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +data=$(cat) +exitcodes="" +hookname=$(basename $0) +GIT_DIR=${GIT_DIR:-$(dirname $0)} + +for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do +test -x "${hook}" && test -f "${hook}" || continue +echo "${data}" | "${hook}" +exitcodes="${exitcodes} $?" +done + +for i in ${exitcodes}; do +[ ${i} -eq 0 ] || exit ${i} +done diff --git a/tests/gitea-repositories-meta/user27/template1.git/hooks/post-receive.d/gitea b/tests/gitea-repositories-meta/user27/template1.git/hooks/post-receive.d/gitea new file mode 100644 index 0000000000..43a948da3a --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/hooks/post-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive diff --git a/tests/gitea-repositories-meta/user27/template1.git/hooks/pre-receive b/tests/gitea-repositories-meta/user27/template1.git/hooks/pre-receive new file mode 100644 index 0000000000..f1f2709ddd --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/hooks/pre-receive @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +data=$(cat) +exitcodes="" +hookname=$(basename $0) +GIT_DIR=${GIT_DIR:-$(dirname $0)} + +for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do +test -x "${hook}" && test -f "${hook}" || continue +echo "${data}" | "${hook}" +exitcodes="${exitcodes} $?" +done + +for i in ${exitcodes}; do +[ ${i} -eq 0 ] || exit ${i} +done diff --git a/tests/gitea-repositories-meta/user27/template1.git/hooks/pre-receive.d/gitea b/tests/gitea-repositories-meta/user27/template1.git/hooks/pre-receive.d/gitea new file mode 100644 index 0000000000..49d0940636 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/hooks/pre-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive diff --git a/tests/gitea-repositories-meta/user27/template1.git/hooks/update b/tests/gitea-repositories-meta/user27/template1.git/hooks/update new file mode 100644 index 0000000000..df5bd27f10 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/hooks/update @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +exitcodes="" +hookname=$(basename $0) +GIT_DIR=${GIT_DIR:-$(dirname $0)} + +for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do +test -x "${hook}" && test -f "${hook}" || continue +"${hook}" $1 $2 $3 +exitcodes="${exitcodes} $?" +done + +for i in ${exitcodes}; do +[ ${i} -eq 0 ] || exit ${i} +done diff --git a/tests/gitea-repositories-meta/user27/template1.git/hooks/update.d/gitea b/tests/gitea-repositories-meta/user27/template1.git/hooks/update.d/gitea new file mode 100644 index 0000000000..38101c2426 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/hooks/update.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3 diff --git a/tests/gitea-repositories-meta/user27/template1.git/info/exclude b/tests/gitea-repositories-meta/user27/template1.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user27/template1.git/info/refs b/tests/gitea-repositories-meta/user27/template1.git/info/refs new file mode 100644 index 0000000000..22f08279c0 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/info/refs @@ -0,0 +1 @@ +aacbdfe9e1c4b47f60abe81849045fa4e96f1d75 refs/heads/master diff --git a/tests/gitea-repositories-meta/user27/template1.git/objects/47/34b1f84a367fa1b81c31aa4234a5bad11cafa3 b/tests/gitea-repositories-meta/user27/template1.git/objects/47/34b1f84a367fa1b81c31aa4234a5bad11cafa3 Binary files differnew file mode 100644 index 0000000000..b6f121a4bb --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/objects/47/34b1f84a367fa1b81c31aa4234a5bad11cafa3 diff --git a/tests/gitea-repositories-meta/user27/template1.git/objects/4d/31f3a12656368a8d9180f431d40d0fc408be2d b/tests/gitea-repositories-meta/user27/template1.git/objects/4d/31f3a12656368a8d9180f431d40d0fc408be2d Binary files differnew file mode 100644 index 0000000000..d2f4c1d04e --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/objects/4d/31f3a12656368a8d9180f431d40d0fc408be2d diff --git a/tests/gitea-repositories-meta/user27/template1.git/objects/51/f84af231345367fd5d61ceb89efb3b6d757061 b/tests/gitea-repositories-meta/user27/template1.git/objects/51/f84af231345367fd5d61ceb89efb3b6d757061 Binary files differnew file mode 100644 index 0000000000..aa34a8a767 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/objects/51/f84af231345367fd5d61ceb89efb3b6d757061 diff --git a/tests/gitea-repositories-meta/user27/template1.git/objects/79/3aa682b06ae032641abf70c5dfeade28c07c52 b/tests/gitea-repositories-meta/user27/template1.git/objects/79/3aa682b06ae032641abf70c5dfeade28c07c52 Binary files differnew file mode 100644 index 0000000000..3f9705fef4 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/objects/79/3aa682b06ae032641abf70c5dfeade28c07c52 diff --git a/tests/gitea-repositories-meta/user27/template1.git/objects/aa/cbdfe9e1c4b47f60abe81849045fa4e96f1d75 b/tests/gitea-repositories-meta/user27/template1.git/objects/aa/cbdfe9e1c4b47f60abe81849045fa4e96f1d75 Binary files differnew file mode 100644 index 0000000000..74419f4b47 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/objects/aa/cbdfe9e1c4b47f60abe81849045fa4e96f1d75 diff --git a/tests/gitea-repositories-meta/user27/template1.git/objects/dd/392e939ea4936b2459219c9c9a1f25547ccaeb b/tests/gitea-repositories-meta/user27/template1.git/objects/dd/392e939ea4936b2459219c9c9a1f25547ccaeb Binary files differnew file mode 100644 index 0000000000..844eb1c2a6 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/objects/dd/392e939ea4936b2459219c9c9a1f25547ccaeb diff --git a/tests/gitea-repositories-meta/user27/template1.git/objects/f2/8eeca3df7614fd4f10c1030f13feb418ef3c6f b/tests/gitea-repositories-meta/user27/template1.git/objects/f2/8eeca3df7614fd4f10c1030f13feb418ef3c6f Binary files differnew file mode 100644 index 0000000000..0699bff833 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/objects/f2/8eeca3df7614fd4f10c1030f13feb418ef3c6f diff --git a/tests/gitea-repositories-meta/user27/template1.git/objects/info/packs b/tests/gitea-repositories-meta/user27/template1.git/objects/info/packs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/objects/info/packs @@ -0,0 +1 @@ + diff --git a/tests/gitea-repositories-meta/user27/template1.git/refs/heads/master b/tests/gitea-repositories-meta/user27/template1.git/refs/heads/master new file mode 100644 index 0000000000..0f13243bfd --- /dev/null +++ b/tests/gitea-repositories-meta/user27/template1.git/refs/heads/master @@ -0,0 +1 @@ +aacbdfe9e1c4b47f60abe81849045fa4e96f1d75 diff --git a/tests/gitea-repositories-meta/user3/repo3.git/HEAD b/tests/gitea-repositories-meta/user3/repo3.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user3/repo3.git/config b/tests/gitea-repositories-meta/user3/repo3.git/config new file mode 100644 index 0000000000..e6da231579 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true diff --git a/tests/gitea-repositories-meta/user3/repo3.git/description b/tests/gitea-repositories-meta/user3/repo3.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user3/repo3.git/hooks/post-receive b/tests/gitea-repositories-meta/user3/repo3.git/hooks/post-receive new file mode 100755 index 0000000000..4b3d452abc --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/hooks/post-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/post-receive.d"`; do + sh "$SHELL_FOLDER/post-receive.d/$i" +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user3/repo3.git/hooks/post-receive.d/gitea b/tests/gitea-repositories-meta/user3/repo3.git/hooks/post-receive.d/gitea new file mode 100755 index 0000000000..43a948da3a --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/hooks/post-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive diff --git a/tests/gitea-repositories-meta/user3/repo3.git/hooks/pre-receive b/tests/gitea-repositories-meta/user3/repo3.git/hooks/pre-receive new file mode 100755 index 0000000000..4127013053 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/hooks/pre-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/pre-receive.d"`; do + sh "$SHELL_FOLDER/pre-receive.d/$i" +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user3/repo3.git/hooks/pre-receive.d/gitea b/tests/gitea-repositories-meta/user3/repo3.git/hooks/pre-receive.d/gitea new file mode 100755 index 0000000000..49d0940636 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/hooks/pre-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive diff --git a/tests/gitea-repositories-meta/user3/repo3.git/hooks/update b/tests/gitea-repositories-meta/user3/repo3.git/hooks/update new file mode 100755 index 0000000000..c186fe4a18 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/hooks/update @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/update.d"`; do + sh "$SHELL_FOLDER/update.d/$i" $1 $2 $3 +done
\ No newline at end of file diff --git a/tests/gitea-repositories-meta/user3/repo3.git/hooks/update.d/gitea b/tests/gitea-repositories-meta/user3/repo3.git/hooks/update.d/gitea new file mode 100755 index 0000000000..38101c2426 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/hooks/update.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3 diff --git a/tests/gitea-repositories-meta/user3/repo3.git/info/exclude b/tests/gitea-repositories-meta/user3/repo3.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user3/repo3.git/objects/20/ade30d25e0ecaeec84e7f542a8456900858240 b/tests/gitea-repositories-meta/user3/repo3.git/objects/20/ade30d25e0ecaeec84e7f542a8456900858240 Binary files differnew file mode 100644 index 0000000000..9f3ffe5f27 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/objects/20/ade30d25e0ecaeec84e7f542a8456900858240 diff --git a/tests/gitea-repositories-meta/user3/repo3.git/objects/27/74debeea6dc742cc4971a92db0e08b95b60588 b/tests/gitea-repositories-meta/user3/repo3.git/objects/27/74debeea6dc742cc4971a92db0e08b95b60588 Binary files differnew file mode 100644 index 0000000000..5d9226f7a1 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/objects/27/74debeea6dc742cc4971a92db0e08b95b60588 diff --git a/tests/gitea-repositories-meta/user3/repo3.git/objects/2a/47ca4b614a9f5a43abbd5ad851a54a616ffee6 b/tests/gitea-repositories-meta/user3/repo3.git/objects/2a/47ca4b614a9f5a43abbd5ad851a54a616ffee6 Binary files differnew file mode 100644 index 0000000000..ca60d2314f --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/objects/2a/47ca4b614a9f5a43abbd5ad851a54a616ffee6 diff --git a/tests/gitea-repositories-meta/user3/repo3.git/objects/2f/9b22fd3159a43b7b4e5dd806fcd544edf8716f b/tests/gitea-repositories-meta/user3/repo3.git/objects/2f/9b22fd3159a43b7b4e5dd806fcd544edf8716f Binary files differnew file mode 100644 index 0000000000..e98d752dad --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/objects/2f/9b22fd3159a43b7b4e5dd806fcd544edf8716f diff --git a/tests/gitea-repositories-meta/user3/repo3.git/objects/d2/2b4d4daa5be07329fcef6ed458f00cf3392da0 b/tests/gitea-repositories-meta/user3/repo3.git/objects/d2/2b4d4daa5be07329fcef6ed458f00cf3392da0 Binary files differnew file mode 100644 index 0000000000..e319f8ce34 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/objects/d2/2b4d4daa5be07329fcef6ed458f00cf3392da0 diff --git a/tests/gitea-repositories-meta/user3/repo3.git/objects/d5/6a3073c1dbb7b15963110a049d50cdb5db99fc b/tests/gitea-repositories-meta/user3/repo3.git/objects/d5/6a3073c1dbb7b15963110a049d50cdb5db99fc Binary files differnew file mode 100644 index 0000000000..eff3c9833e --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/objects/d5/6a3073c1dbb7b15963110a049d50cdb5db99fc diff --git a/tests/gitea-repositories-meta/user3/repo3.git/objects/ec/f0db3c1ec806522de4b491fb9a3c7457398c61 b/tests/gitea-repositories-meta/user3/repo3.git/objects/ec/f0db3c1ec806522de4b491fb9a3c7457398c61 Binary files differnew file mode 100644 index 0000000000..ed431f70d3 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/objects/ec/f0db3c1ec806522de4b491fb9a3c7457398c61 diff --git a/tests/gitea-repositories-meta/user3/repo3.git/objects/ee/16d127df463aa491e08958120f2108b02468df b/tests/gitea-repositories-meta/user3/repo3.git/objects/ee/16d127df463aa491e08958120f2108b02468df Binary files differnew file mode 100644 index 0000000000..e177f69e37 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/objects/ee/16d127df463aa491e08958120f2108b02468df diff --git a/tests/gitea-repositories-meta/user3/repo3.git/refs/heads/master b/tests/gitea-repositories-meta/user3/repo3.git/refs/heads/master new file mode 100644 index 0000000000..ccee722d52 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/refs/heads/master @@ -0,0 +1 @@ +2a47ca4b614a9f5a43abbd5ad851a54a616ffee6 diff --git a/tests/gitea-repositories-meta/user3/repo3.git/refs/heads/test_branch b/tests/gitea-repositories-meta/user3/repo3.git/refs/heads/test_branch new file mode 100644 index 0000000000..dfe0c6a128 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo3.git/refs/heads/test_branch @@ -0,0 +1 @@ +d22b4d4daa5be07329fcef6ed458f00cf3392da0 diff --git a/tests/gitea-repositories-meta/user30/empty.git/HEAD b/tests/gitea-repositories-meta/user30/empty.git/HEAD new file mode 100644 index 0000000000..b870d82622 --- /dev/null +++ b/tests/gitea-repositories-meta/user30/empty.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/tests/gitea-repositories-meta/user30/empty.git/config b/tests/gitea-repositories-meta/user30/empty.git/config new file mode 100644 index 0000000000..7c968c3b5a --- /dev/null +++ b/tests/gitea-repositories-meta/user30/empty.git/config @@ -0,0 +1,6 @@ +[core] + bare = true + repositoryformatversion = 0 + filemode = false + symlinks = false + ignorecase = true diff --git a/tests/gitea-repositories-meta/user5/repo4.git/HEAD b/tests/gitea-repositories-meta/user5/repo4.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user5/repo4.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user5/repo4.git/config b/tests/gitea-repositories-meta/user5/repo4.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/tests/gitea-repositories-meta/user5/repo4.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/tests/gitea-repositories-meta/user5/repo4.git/description b/tests/gitea-repositories-meta/user5/repo4.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user5/repo4.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user5/repo4.git/info/exclude b/tests/gitea-repositories-meta/user5/repo4.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user5/repo4.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user5/repo4.git/objects/16/dfebd1ed3905d78d7e061e945fc9c34afe4e81 b/tests/gitea-repositories-meta/user5/repo4.git/objects/16/dfebd1ed3905d78d7e061e945fc9c34afe4e81 Binary files differnew file mode 100644 index 0000000000..76d765ea90 --- /dev/null +++ b/tests/gitea-repositories-meta/user5/repo4.git/objects/16/dfebd1ed3905d78d7e061e945fc9c34afe4e81 diff --git a/tests/gitea-repositories-meta/user5/repo4.git/objects/c1/202ad022ae7d3a6d2474dc76d5a0c8e87cdc0f b/tests/gitea-repositories-meta/user5/repo4.git/objects/c1/202ad022ae7d3a6d2474dc76d5a0c8e87cdc0f Binary files differnew file mode 100644 index 0000000000..f63d6019b8 --- /dev/null +++ b/tests/gitea-repositories-meta/user5/repo4.git/objects/c1/202ad022ae7d3a6d2474dc76d5a0c8e87cdc0f diff --git a/tests/gitea-repositories-meta/user5/repo4.git/objects/c7/cd3cd144e6d23c9d6f3d07e52b2c1a956e0338 b/tests/gitea-repositories-meta/user5/repo4.git/objects/c7/cd3cd144e6d23c9d6f3d07e52b2c1a956e0338 Binary files differnew file mode 100644 index 0000000000..c8d7c54d58 --- /dev/null +++ b/tests/gitea-repositories-meta/user5/repo4.git/objects/c7/cd3cd144e6d23c9d6f3d07e52b2c1a956e0338 diff --git a/tests/gitea-repositories-meta/user5/repo4.git/refs/heads/master b/tests/gitea-repositories-meta/user5/repo4.git/refs/heads/master new file mode 100644 index 0000000000..5fd26e37da --- /dev/null +++ b/tests/gitea-repositories-meta/user5/repo4.git/refs/heads/master @@ -0,0 +1 @@ +c7cd3cd144e6d23c9d6f3d07e52b2c1a956e0338 diff --git a/tests/integration/README.md b/tests/integration/README.md new file mode 100644 index 0000000000..636949df38 --- /dev/null +++ b/tests/integration/README.md @@ -0,0 +1,96 @@ +# Integration tests + +Integration tests can be run with make commands for the +appropriate backends, namely: +```shell +make test-sqlite +make test-pgsql +make test-mysql +make test-mysql8 +make test-mssql +``` + +Make sure to perform a clean build before running tests: +``` +make clean build +``` + +## Run all tests via local drone +``` +drone exec --local --build-event "pull_request" +``` + +## Run sqlite integration tests +Start tests +``` +make test-sqlite +``` + +## Run MySQL integration tests +Setup a MySQL database inside docker +``` +docker run -e "MYSQL_DATABASE=test" -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" -p 3306:3306 --rm --name mysql mysql:latest #(just ctrl-c to stop db and clean the container) +docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" --rm --name elasticsearch elasticsearch:7.6.0 #(in a second terminal, just ctrl-c to stop db and clean the container) +``` +Start tests based on the database container +``` +TEST_MYSQL_HOST=localhost:3306 TEST_MYSQL_DBNAME=test TEST_MYSQL_USERNAME=root TEST_MYSQL_PASSWORD='' make test-mysql +``` + +## Run pgsql integration tests +Setup a pgsql database inside docker +``` +docker run -e "POSTGRES_DB=test" -p 5432:5432 --rm --name pgsql postgres:latest #(just ctrl-c to stop db and clean the container) +``` +Start tests based on the database container +``` +TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=test TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-pgsql +``` + +## Run mssql integration tests +Setup a mssql database inside docker +``` +docker run -e "ACCEPT_EULA=Y" -e "MSSQL_PID=Standard" -e "SA_PASSWORD=MwantsaSecurePassword1" -p 1433:1433 --rm --name mssql microsoft/mssql-server-linux:latest #(just ctrl-c to stop db and clean the container) +``` +Start tests based on the database container +``` +TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=gitea_test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-mssql +``` + +## Running individual tests + +Example command to run GPG test: + +For SQLite: + +``` +make test-sqlite#GPG +``` + +For other databases(replace `mssql` to `mysql`, `mysql8` or `pgsql`): + +``` +TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-mssql#GPG +``` + +## Setting timeouts for declaring long-tests and long-flushes + +We appreciate that some testing machines may not be very powerful and +the default timeouts for declaring a slow test or a slow clean-up flush +may not be appropriate. + +You can either: + +* Within the test ini file set the following section: + +```ini +[integration-tests] +SLOW_TEST = 10s ; 10s is the default value +SLOW_FLUSH = 5S ; 5s is the default value +``` + +* Set the following environment variables: + +```bash +GITEA_SLOW_TEST_TIME="10s" GITEA_SLOW_FLUSH_TIME="5s" make test-sqlite +``` diff --git a/tests/integration/README_ZH.md b/tests/integration/README_ZH.md new file mode 100644 index 0000000000..3840232472 --- /dev/null +++ b/tests/integration/README_ZH.md @@ -0,0 +1,71 @@ +# 关于集æˆæµ‹è¯• + +使用如下 make 命令å¯ä»¥è¿è¡ŒæŒ‡å®šçš„集æˆæµ‹è¯•ï¼š +```shell +make test-mysql +make test-pgsql +make test-sqlite +``` + +在执行集æˆæµ‹è¯•å‘½ä»¤å‰è¯·ç¡®ä¿æ¸…ç†äº†ä¹‹å‰çš„构建环境,清ç†å‘½ä»¤å¦‚下: +``` +make clean build +``` + +## 如何在本地 drone æœåŠ¡å™¨ä¸Šè¿è¡Œæ‰€æœ‰æµ‹è¯• +``` +drone exec --local --build-event "pull_request" +``` + +## 如何使用 sqlite æ•°æ®åº“进行集æˆæµ‹è¯• +使用该命令执行集æˆæµ‹è¯• +``` +make test-sqlite +``` + +## 如何使用 mysql æ•°æ®åº“进行集æˆæµ‹è¯• +首先在docker容器里部署一个 mysql æ•°æ®åº“ +``` +docker run -e "MYSQL_DATABASE=test" -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" -p 3306:3306 --rm --name mysql mysql:8 #(just ctrl-c to stop db and clean the container) +``` +之åŽä¾¿å¯ä»¥åŸºäºŽè¿™ä¸ªæ•°æ®åº“进行集æˆæµ‹è¯• +``` +TEST_MYSQL_HOST=localhost:3306 TEST_MYSQL_DBNAME=test TEST_MYSQL_USERNAME=root TEST_MYSQL_PASSWORD='' make test-mysql +``` + +## 如何使用 pgsql æ•°æ®åº“进行集æˆæµ‹è¯• +åŒä¸Šï¼Œé¦–先在 docker 容器里部署一个 pgsql æ•°æ®åº“ +``` +docker run -e "POSTGRES_DB=test" -p 5432:5432 --rm --name pgsql postgres:14 #(just ctrl-c to stop db and clean the container) +``` +之åŽä¾¿å¯ä»¥åŸºäºŽè¿™ä¸ªæ•°æ®åº“进行集æˆæµ‹è¯• +``` +TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=test TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-pgsql +``` + +## Run mssql integration tests +åŒä¸Šï¼Œé¦–先在 docker 容器里部署一个 mssql æ•°æ®åº“ +``` +docker run -e "ACCEPT_EULA=Y" -e "MSSQL_PID=Standard" -e "SA_PASSWORD=MwantsaSecurePassword1" -p 1433:1433 --rm --name mssql microsoft/mssql-server-linux:latest #(just ctrl-c to stop db and clean the container) +``` +之åŽä¾¿å¯ä»¥åŸºäºŽè¿™ä¸ªæ•°æ®åº“进行集æˆæµ‹è¯• +``` +TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=gitea_test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-mssql +``` + +## 如何进行自定义的集æˆæµ‹è¯• + +下é¢çš„ç¤ºä¾‹å±•ç¤ºäº†æ€Žæ ·åœ¨é›†æˆæµ‹è¯•ä¸åªè¿›è¡Œ GPG 测试: + +sqlite æ•°æ®åº“: + +``` +make test-sqlite#GPG +``` + +其它数æ®åº“(把 MSSQL 替æ¢ä¸º MYSQL, MYSQL8, PGSQL): + +``` +TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-mssql#GPG +``` + diff --git a/tests/integration/admin_user_test.go b/tests/integration/admin_user_test.go new file mode 100644 index 0000000000..ffe3f670fe --- /dev/null +++ b/tests/integration/admin_user_test.go @@ -0,0 +1,84 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "strconv" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAdminViewUsers(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + req := NewRequest(t, "GET", "/admin/users") + session.MakeRequest(t, req, http.StatusOK) + + session = loginUser(t, "user2") + req = NewRequest(t, "GET", "/admin/users") + session.MakeRequest(t, req, http.StatusForbidden) +} + +func TestAdminViewUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + req := NewRequest(t, "GET", "/admin/users/1") + session.MakeRequest(t, req, http.StatusOK) + + session = loginUser(t, "user2") + req = NewRequest(t, "GET", "/admin/users/1") + session.MakeRequest(t, req, http.StatusForbidden) +} + +func TestAdminEditUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + testSuccessfullEdit(t, user_model.User{ID: 2, Name: "newusername", LoginName: "otherlogin", Email: "new@e-mail.gitea"}) +} + +func testSuccessfullEdit(t *testing.T, formData user_model.User) { + makeRequest(t, formData, http.StatusSeeOther) +} + +func makeRequest(t *testing.T, formData user_model.User, headerCode int) { + session := loginUser(t, "user1") + csrf := GetCSRF(t, session, "/admin/users/"+strconv.Itoa(int(formData.ID))) + req := NewRequestWithValues(t, "POST", "/admin/users/"+strconv.Itoa(int(formData.ID)), map[string]string{ + "_csrf": csrf, + "user_name": formData.Name, + "login_name": formData.LoginName, + "login_type": "0-0", + "email": formData.Email, + }) + + session.MakeRequest(t, req, headerCode) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: formData.ID}) + assert.Equal(t, formData.Name, user.Name) + assert.Equal(t, formData.LoginName, user.LoginName) + assert.Equal(t, formData.Email, user.Email) +} + +func TestAdminDeleteUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + + csrf := GetCSRF(t, session, "/admin/users/8") + req := NewRequestWithValues(t, "POST", "/admin/users/8/delete", map[string]string{ + "_csrf": csrf, + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + assertUserDeleted(t, 8) + unittest.CheckConsistencyFor(t, &user_model.User{}) +} diff --git a/tests/integration/api_activitypub_person_test.go b/tests/integration/api_activitypub_person_test.go new file mode 100644 index 0000000000..e7ef79d156 --- /dev/null +++ b/tests/integration/api_activitypub_person_test.go @@ -0,0 +1,113 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/activitypub" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/routers" + + ap "github.com/go-ap/activitypub" + "github.com/stretchr/testify/assert" +) + +func TestActivityPubPerson(t *testing.T) { + setting.Federation.Enabled = true + c = routers.NormalRoutes(context.TODO()) + defer func() { + setting.Federation.Enabled = false + c = routers.NormalRoutes(context.TODO()) + }() + + onGiteaRun(t, func(*testing.T, *url.URL) { + username := "user2" + req := NewRequestf(t, "GET", fmt.Sprintf("/api/v1/activitypub/user/%s", username)) + resp := MakeRequest(t, req, http.StatusOK) + body := resp.Body.Bytes() + assert.Contains(t, string(body), "@context") + + var person ap.Person + err := person.UnmarshalJSON(body) + assert.NoError(t, err) + + assert.Equal(t, ap.PersonType, person.Type) + assert.Equal(t, username, person.PreferredUsername.String()) + keyID := person.GetID().String() + assert.Regexp(t, fmt.Sprintf("activitypub/user/%s$", username), keyID) + assert.Regexp(t, fmt.Sprintf("activitypub/user/%s/outbox$", username), person.Outbox.GetID().String()) + assert.Regexp(t, fmt.Sprintf("activitypub/user/%s/inbox$", username), person.Inbox.GetID().String()) + + pubKey := person.PublicKey + assert.NotNil(t, pubKey) + publicKeyID := keyID + "#main-key" + assert.Equal(t, pubKey.ID.String(), publicKeyID) + + pubKeyPem := pubKey.PublicKeyPem + assert.NotNil(t, pubKeyPem) + assert.Regexp(t, "^-----BEGIN PUBLIC KEY-----", pubKeyPem) + }) +} + +func TestActivityPubMissingPerson(t *testing.T) { + setting.Federation.Enabled = true + c = routers.NormalRoutes(context.TODO()) + defer func() { + setting.Federation.Enabled = false + c = routers.NormalRoutes(context.TODO()) + }() + + onGiteaRun(t, func(*testing.T, *url.URL) { + req := NewRequestf(t, "GET", "/api/v1/activitypub/user/nonexistentuser") + resp := MakeRequest(t, req, http.StatusNotFound) + assert.Contains(t, resp.Body.String(), "user redirect does not exist") + }) +} + +func TestActivityPubPersonInbox(t *testing.T) { + setting.Federation.Enabled = true + c = routers.NormalRoutes(context.TODO()) + defer func() { + setting.Federation.Enabled = false + c = routers.NormalRoutes(context.TODO()) + }() + + srv := httptest.NewServer(c) + defer srv.Close() + + onGiteaRun(t, func(*testing.T, *url.URL) { + appURL := setting.AppURL + setting.AppURL = srv.URL + defer func() { + setting.Database.LogSQL = false + setting.AppURL = appURL + }() + username1 := "user1" + ctx := context.Background() + user1, err := user_model.GetUserByName(ctx, username1) + assert.NoError(t, err) + user1url := fmt.Sprintf("%s/api/v1/activitypub/user/%s#main-key", srv.URL, username1) + c, err := activitypub.NewClient(user1, user1url) + assert.NoError(t, err) + username2 := "user2" + user2inboxurl := fmt.Sprintf("%s/api/v1/activitypub/user/%s/inbox", srv.URL, username2) + + // Signed request succeeds + resp, err := c.Post([]byte{}, user2inboxurl) + assert.NoError(t, err) + assert.Equal(t, http.StatusNoContent, resp.StatusCode) + + // Unsigned request fails + req := NewRequest(t, "POST", user2inboxurl) + MakeRequest(t, req, http.StatusInternalServerError) + }) +} diff --git a/tests/integration/api_admin_org_test.go b/tests/integration/api_admin_org_test.go new file mode 100644 index 0000000000..720f6fc6b6 --- /dev/null +++ b/tests/integration/api_admin_org_test.go @@ -0,0 +1,88 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "strings" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIAdminOrgCreate(t *testing.T) { + onGiteaRun(t, func(*testing.T, *url.URL) { + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session) + + org := api.CreateOrgOption{ + UserName: "user2_org", + FullName: "User2's organization", + Description: "This organization created by admin for user2", + Website: "https://try.gitea.io", + Location: "Shanghai", + Visibility: "private", + } + req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs?token="+token, &org) + resp := session.MakeRequest(t, req, http.StatusCreated) + + var apiOrg api.Organization + DecodeJSON(t, resp, &apiOrg) + + assert.Equal(t, org.UserName, apiOrg.UserName) + assert.Equal(t, org.FullName, apiOrg.FullName) + assert.Equal(t, org.Description, apiOrg.Description) + assert.Equal(t, org.Website, apiOrg.Website) + assert.Equal(t, org.Location, apiOrg.Location) + assert.Equal(t, org.Visibility, apiOrg.Visibility) + + unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: org.UserName, + LowerName: strings.ToLower(org.UserName), + FullName: org.FullName, + }) + }) +} + +func TestAPIAdminOrgCreateBadVisibility(t *testing.T) { + onGiteaRun(t, func(*testing.T, *url.URL) { + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session) + + org := api.CreateOrgOption{ + UserName: "user2_org", + FullName: "User2's organization", + Description: "This organization created by admin for user2", + Website: "https://try.gitea.io", + Location: "Shanghai", + Visibility: "notvalid", + } + req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs?token="+token, &org) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + }) +} + +func TestAPIAdminOrgCreateNotAdmin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + nonAdminUsername := "user2" + session := loginUser(t, nonAdminUsername) + token := getTokenForLoggedInUser(t, session) + org := api.CreateOrgOption{ + UserName: "user2_org", + FullName: "User2's organization", + Description: "This organization created by admin for user2", + Website: "https://try.gitea.io", + Location: "Shanghai", + Visibility: "public", + } + req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs?token="+token, &org) + session.MakeRequest(t, req, http.StatusForbidden) +} diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go new file mode 100644 index 0000000000..dea0bdd063 --- /dev/null +++ b/tests/integration/api_admin_test.go @@ -0,0 +1,211 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + asymkey_model "code.gitea.io/gitea/models/asymkey" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/json" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) { + defer tests.PrepareTestEnv(t)() + // user1 is an admin user + session := loginUser(t, "user1") + keyOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + + token := getTokenForLoggedInUser(t, session) + urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys?token=%s", keyOwner.Name, token) + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", + "title": "test-key", + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + + var newPublicKey api.PublicKey + DecodeJSON(t, resp, &newPublicKey) + unittest.AssertExistsAndLoadBean(t, &asymkey_model.PublicKey{ + ID: newPublicKey.ID, + Name: newPublicKey.Title, + Fingerprint: newPublicKey.Fingerprint, + OwnerID: keyOwner.ID, + }) + + req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d?token=%s", + keyOwner.Name, newPublicKey.ID, token) + session.MakeRequest(t, req, http.StatusNoContent) + unittest.AssertNotExistsBean(t, &asymkey_model.PublicKey{ID: newPublicKey.ID}) +} + +func TestAPIAdminDeleteMissingSSHKey(t *testing.T) { + defer tests.PrepareTestEnv(t)() + // user1 is an admin user + session := loginUser(t, "user1") + + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "DELETE", "/api/v1/admin/users/user1/keys/%d?token=%s", unittest.NonexistentID, token) + session.MakeRequest(t, req, http.StatusNotFound) +} + +func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) { + defer tests.PrepareTestEnv(t)() + adminUsername := "user1" + normalUsername := "user2" + session := loginUser(t, adminUsername) + + token := getTokenForLoggedInUser(t, session) + urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys?token=%s", adminUsername, token) + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", + "title": "test-key", + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + var newPublicKey api.PublicKey + DecodeJSON(t, resp, &newPublicKey) + + session = loginUser(t, normalUsername) + token = getTokenForLoggedInUser(t, session) + req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d?token=%s", + adminUsername, newPublicKey.ID, token) + session.MakeRequest(t, req, http.StatusForbidden) +} + +func TestAPISudoUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + adminUsername := "user1" + normalUsername := "user2" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session) + + urlStr := fmt.Sprintf("/api/v1/user?sudo=%s&token=%s", normalUsername, token) + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + var user api.User + DecodeJSON(t, resp, &user) + + assert.Equal(t, normalUsername, user.UserName) +} + +func TestAPISudoUserForbidden(t *testing.T) { + defer tests.PrepareTestEnv(t)() + adminUsername := "user1" + normalUsername := "user2" + + session := loginUser(t, normalUsername) + token := getTokenForLoggedInUser(t, session) + + urlStr := fmt.Sprintf("/api/v1/user?sudo=%s&token=%s", adminUsername, token) + req := NewRequest(t, "GET", urlStr) + session.MakeRequest(t, req, http.StatusForbidden) +} + +func TestAPIListUsers(t *testing.T) { + defer tests.PrepareTestEnv(t)() + adminUsername := "user1" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session) + + urlStr := fmt.Sprintf("/api/v1/admin/users?token=%s", token) + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + var users []api.User + DecodeJSON(t, resp, &users) + + found := false + for _, user := range users { + if user.UserName == adminUsername { + found = true + } + } + assert.True(t, found) + numberOfUsers := unittest.GetCount(t, &user_model.User{}, "type = 0") + assert.Equal(t, numberOfUsers, len(users)) +} + +func TestAPIListUsersNotLoggedIn(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequest(t, "GET", "/api/v1/admin/users") + MakeRequest(t, req, http.StatusUnauthorized) +} + +func TestAPIListUsersNonAdmin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + nonAdminUsername := "user2" + session := loginUser(t, nonAdminUsername) + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "GET", "/api/v1/admin/users?token=%s", token) + session.MakeRequest(t, req, http.StatusForbidden) +} + +func TestAPICreateUserInvalidEmail(t *testing.T) { + defer tests.PrepareTestEnv(t)() + adminUsername := "user1" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session) + urlStr := fmt.Sprintf("/api/v1/admin/users?token=%s", token) + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "email": "invalid_email@domain.com\r\n", + "full_name": "invalid user", + "login_name": "invalidUser", + "must_change_password": "true", + "password": "password", + "send_notify": "true", + "source_id": "0", + "username": "invalidUser", + }) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) +} + +func TestAPIEditUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + adminUsername := "user1" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session) + urlStr := fmt.Sprintf("/api/v1/admin/users/%s?token=%s", "user2", token) + + req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ + // required + "login_name": "user2", + "source_id": "0", + // to change + "full_name": "Full Name User 2", + }) + session.MakeRequest(t, req, http.StatusOK) + + empty := "" + req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ + LoginName: "user2", + SourceID: 0, + Email: &empty, + }) + resp := session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + errMap := make(map[string]interface{}) + json.Unmarshal(resp.Body.Bytes(), &errMap) + assert.EqualValues(t, "email is not allowed to be empty string", errMap["message"].(string)) + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "user2"}) + assert.False(t, user2.IsRestricted) + bTrue := true + req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ + // required + LoginName: "user2", + SourceID: 0, + // to change + Restricted: &bTrue, + }) + session.MakeRequest(t, req, http.StatusOK) + user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "user2"}) + assert.True(t, user2.IsRestricted) +} diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go new file mode 100644 index 0000000000..bdfdd3c752 --- /dev/null +++ b/tests/integration/api_branch_test.go @@ -0,0 +1,201 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "testing" + + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func testAPIGetBranch(t *testing.T, branchName string, exists bool) { + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/branches/%s?token=%s", branchName, token) + resp := session.MakeRequest(t, req, NoExpectedStatus) + if !exists { + assert.EqualValues(t, http.StatusNotFound, resp.Code) + return + } + assert.EqualValues(t, http.StatusOK, resp.Code) + var branch api.Branch + DecodeJSON(t, resp, &branch) + assert.EqualValues(t, branchName, branch.Name) + assert.True(t, branch.UserCanPush) + assert.True(t, branch.UserCanMerge) +} + +func testAPIGetBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) { + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/branch_protections/%s?token=%s", branchName, token) + resp := session.MakeRequest(t, req, expectedHTTPStatus) + + if resp.Code == http.StatusOK { + var branchProtection api.BranchProtection + DecodeJSON(t, resp, &branchProtection) + assert.EqualValues(t, branchName, branchProtection.BranchName) + } +} + +func testAPICreateBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) { + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/branch_protections?token="+token, &api.BranchProtection{ + BranchName: branchName, + }) + resp := session.MakeRequest(t, req, expectedHTTPStatus) + + if resp.Code == http.StatusCreated { + var branchProtection api.BranchProtection + DecodeJSON(t, resp, &branchProtection) + assert.EqualValues(t, branchName, branchProtection.BranchName) + } +} + +func testAPIEditBranchProtection(t *testing.T, branchName string, body *api.BranchProtection, expectedHTTPStatus int) { + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequestWithJSON(t, "PATCH", "/api/v1/repos/user2/repo1/branch_protections/"+branchName+"?token="+token, body) + resp := session.MakeRequest(t, req, expectedHTTPStatus) + + if resp.Code == http.StatusOK { + var branchProtection api.BranchProtection + DecodeJSON(t, resp, &branchProtection) + assert.EqualValues(t, branchName, branchProtection.BranchName) + } +} + +func testAPIDeleteBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) { + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "DELETE", "/api/v1/repos/user2/repo1/branch_protections/%s?token=%s", branchName, token) + session.MakeRequest(t, req, expectedHTTPStatus) +} + +func testAPIDeleteBranch(t *testing.T, branchName string, expectedHTTPStatus int) { + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "DELETE", "/api/v1/repos/user2/repo1/branches/%s?token=%s", branchName, token) + session.MakeRequest(t, req, expectedHTTPStatus) +} + +func TestAPIGetBranch(t *testing.T) { + defer tests.PrepareTestEnv(t)() + for _, test := range []struct { + BranchName string + Exists bool + }{ + {"master", true}, + {"master/doesnotexist", false}, + {"feature/1", true}, + {"feature/1/doesnotexist", false}, + } { + testAPIGetBranch(t, test.BranchName, test.Exists) + } +} + +func TestAPICreateBranch(t *testing.T) { + onGiteaRun(t, testAPICreateBranches) +} + +func testAPICreateBranches(t *testing.T, giteaURL *url.URL) { + username := "user2" + ctx := NewAPITestContext(t, username, "my-noo-repo") + giteaURL.Path = ctx.GitPath() + + t.Run("CreateRepo", doAPICreateRepository(ctx, false)) + testCases := []struct { + OldBranch string + NewBranch string + ExpectedHTTPStatus int + }{ + // Creating branch from default branch + { + OldBranch: "", + NewBranch: "new_branch_from_default_branch", + ExpectedHTTPStatus: http.StatusCreated, + }, + // Creating branch from master + { + OldBranch: "master", + NewBranch: "new_branch_from_master_1", + ExpectedHTTPStatus: http.StatusCreated, + }, + // Trying to create from master but already exists + { + OldBranch: "master", + NewBranch: "new_branch_from_master_1", + ExpectedHTTPStatus: http.StatusConflict, + }, + // Trying to create from other branch (not default branch) + { + OldBranch: "new_branch_from_master_1", + NewBranch: "branch_2", + ExpectedHTTPStatus: http.StatusCreated, + }, + // Trying to create from a branch which does not exist + { + OldBranch: "does_not_exist", + NewBranch: "new_branch_from_non_existent", + ExpectedHTTPStatus: http.StatusNotFound, + }, + } + for _, test := range testCases { + defer tests.ResetFixtures(t) + session := ctx.Session + testAPICreateBranch(t, session, "user2", "my-noo-repo", test.OldBranch, test.NewBranch, test.ExpectedHTTPStatus) + } +} + +func testAPICreateBranch(t testing.TB, session *TestSession, user, repo, oldBranch, newBranch string, status int) bool { + token := getTokenForLoggedInUser(t, session) + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+user+"/"+repo+"/branches?token="+token, &api.CreateBranchRepoOption{ + BranchName: newBranch, + OldBranchName: oldBranch, + }) + resp := session.MakeRequest(t, req, status) + + var branch api.Branch + DecodeJSON(t, resp, &branch) + + if status == http.StatusCreated { + assert.EqualValues(t, newBranch, branch.Name) + } + + return resp.Result().StatusCode == status +} + +func TestAPIBranchProtection(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // Branch protection only on branch that exist + testAPICreateBranchProtection(t, "master/doesnotexist", http.StatusNotFound) + // Get branch protection on branch that exist but not branch protection + testAPIGetBranchProtection(t, "master", http.StatusNotFound) + + testAPICreateBranchProtection(t, "master", http.StatusCreated) + // Can only create once + testAPICreateBranchProtection(t, "master", http.StatusForbidden) + + // Can't delete a protected branch + testAPIDeleteBranch(t, "master", http.StatusForbidden) + + testAPIGetBranchProtection(t, "master", http.StatusOK) + testAPIEditBranchProtection(t, "master", &api.BranchProtection{ + EnablePush: true, + }, http.StatusOK) + + testAPIDeleteBranchProtection(t, "master", http.StatusNoContent) + + // Test branch deletion + testAPIDeleteBranch(t, "master", http.StatusForbidden) + testAPIDeleteBranch(t, "branch2", http.StatusNoContent) +} diff --git a/tests/integration/api_comment_test.go b/tests/integration/api_comment_test.go new file mode 100644 index 0000000000..126d886842 --- /dev/null +++ b/tests/integration/api_comment_test.go @@ -0,0 +1,205 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "testing" + + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/convert" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIListRepoComments(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{}, + unittest.Cond("type = ?", issues_model.CommentTypeComment)) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, repoOwner.Name) + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments", repoOwner.Name, repo.Name)) + req := NewRequest(t, "GET", link.String()) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiComments []*api.Comment + DecodeJSON(t, resp, &apiComments) + assert.Len(t, apiComments, 2) + for _, apiComment := range apiComments { + c := &issues_model.Comment{ID: apiComment.ID} + unittest.AssertExistsAndLoadBean(t, c, + unittest.Cond("type = ?", issues_model.CommentTypeComment)) + unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: c.IssueID, RepoID: repo.ID}) + } + + // test before and since filters + query := url.Values{} + before := "2000-01-01T00:00:11+00:00" // unix: 946684811 + since := "2000-01-01T00:00:12+00:00" // unix: 946684812 + query.Add("before", before) + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiComments) + assert.Len(t, apiComments, 1) + assert.EqualValues(t, 2, apiComments[0].ID) + + query.Del("before") + query.Add("since", since) + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiComments) + assert.Len(t, apiComments, 1) + assert.EqualValues(t, 3, apiComments[0].ID) +} + +func TestAPIListIssueComments(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{}, + unittest.Cond("type = ?", issues_model.CommentTypeComment)) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, repoOwner.Name) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/comments", + repoOwner.Name, repo.Name, issue.Index) + resp := session.MakeRequest(t, req, http.StatusOK) + + var comments []*api.Comment + DecodeJSON(t, resp, &comments) + expectedCount := unittest.GetCount(t, &issues_model.Comment{IssueID: issue.ID}, + unittest.Cond("type = ?", issues_model.CommentTypeComment)) + assert.EqualValues(t, expectedCount, len(comments)) +} + +func TestAPICreateComment(t *testing.T) { + defer tests.PrepareTestEnv(t)() + const commentBody = "Comment body" + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, repoOwner.Name) + token := getTokenForLoggedInUser(t, session) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments?token=%s", + repoOwner.Name, repo.Name, issue.Index, token) + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "body": commentBody, + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + + var updatedComment api.Comment + DecodeJSON(t, resp, &updatedComment) + assert.EqualValues(t, commentBody, updatedComment.Body) + unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: updatedComment.ID, IssueID: issue.ID, Content: commentBody}) +} + +func TestAPIGetComment(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) + assert.NoError(t, comment.LoadIssue()) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: comment.Issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, repoOwner.Name) + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID) + session.MakeRequest(t, req, http.StatusOK) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, token) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiComment api.Comment + DecodeJSON(t, resp, &apiComment) + + assert.NoError(t, comment.LoadPoster()) + expect := convert.ToComment(comment) + + assert.Equal(t, expect.ID, apiComment.ID) + assert.Equal(t, expect.Poster.FullName, apiComment.Poster.FullName) + assert.Equal(t, expect.Body, apiComment.Body) + assert.Equal(t, expect.Created.Unix(), apiComment.Created.Unix()) +} + +func TestAPIEditComment(t *testing.T) { + defer tests.PrepareTestEnv(t)() + const newCommentBody = "This is the new comment body" + + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{}, + unittest.Cond("type = ?", issues_model.CommentTypeComment)) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, repoOwner.Name) + token := getTokenForLoggedInUser(t, session) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d?token=%s", + repoOwner.Name, repo.Name, comment.ID, token) + req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ + "body": newCommentBody, + }) + resp := session.MakeRequest(t, req, http.StatusOK) + + var updatedComment api.Comment + DecodeJSON(t, resp, &updatedComment) + assert.EqualValues(t, comment.ID, updatedComment.ID) + assert.EqualValues(t, newCommentBody, updatedComment.Body) + unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID, IssueID: issue.ID, Content: newCommentBody}) +} + +func TestAPIDeleteComment(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{}, + unittest.Cond("type = ?", issues_model.CommentTypeComment)) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, repoOwner.Name) + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/comments/%d?token=%s", + repoOwner.Name, repo.Name, comment.ID, token) + session.MakeRequest(t, req, http.StatusNoContent) + + unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: comment.ID}) +} + +func TestAPIListIssueTimeline(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // load comment + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + // make request + session := loginUser(t, repoOwner.Name) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/timeline", + repoOwner.Name, repo.Name, issue.Index) + resp := session.MakeRequest(t, req, http.StatusOK) + + // check if lens of list returned by API and + // lists extracted directly from DB are the same + var comments []*api.TimelineComment + DecodeJSON(t, resp, &comments) + expectedCount := unittest.GetCount(t, &issues_model.Comment{IssueID: issue.ID}) + assert.EqualValues(t, expectedCount, len(comments)) +} diff --git a/tests/integration/api_fork_test.go b/tests/integration/api_fork_test.go new file mode 100644 index 0000000000..131dcf70bb --- /dev/null +++ b/tests/integration/api_fork_test.go @@ -0,0 +1,19 @@ +// Copyright 2017 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" +) + +func TestCreateForkNoLogin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/forks", &api.CreateForkOption{}) + MakeRequest(t, req, http.StatusUnauthorized) +} diff --git a/tests/integration/api_gpg_keys_test.go b/tests/integration/api_gpg_keys_test.go new file mode 100644 index 0000000000..0ad876c9b9 --- /dev/null +++ b/tests/integration/api_gpg_keys_test.go @@ -0,0 +1,264 @@ +// Copyright 2017 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/http/httptest" + "strconv" + "testing" + + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +type makeRequestFunc func(testing.TB, *http.Request, int) *httptest.ResponseRecorder + +func TestGPGKeys(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + + tt := []struct { + name string + makeRequest makeRequestFunc + token string + results []int + }{ + { + name: "NoLogin", makeRequest: MakeRequest, token: "", + results: []int{http.StatusUnauthorized, http.StatusUnauthorized, http.StatusUnauthorized, http.StatusUnauthorized, http.StatusUnauthorized, http.StatusUnauthorized, http.StatusUnauthorized, http.StatusUnauthorized, http.StatusUnauthorized}, + }, + { + name: "LoggedAsUser2", makeRequest: session.MakeRequest, token: token, + results: []int{http.StatusOK, http.StatusOK, http.StatusNotFound, http.StatusNoContent, http.StatusUnprocessableEntity, http.StatusNotFound, http.StatusCreated, http.StatusNotFound, http.StatusCreated}, + }, + } + + for _, tc := range tt { + // Basic test on result code + t.Run(tc.name, func(t *testing.T) { + t.Run("ViewOwnGPGKeys", func(t *testing.T) { + testViewOwnGPGKeys(t, tc.makeRequest, tc.token, tc.results[0]) + }) + t.Run("ViewGPGKeys", func(t *testing.T) { + testViewGPGKeys(t, tc.makeRequest, tc.token, tc.results[1]) + }) + t.Run("GetGPGKey", func(t *testing.T) { + testGetGPGKey(t, tc.makeRequest, tc.token, tc.results[2]) + }) + t.Run("DeleteGPGKey", func(t *testing.T) { + testDeleteGPGKey(t, tc.makeRequest, tc.token, tc.results[3]) + }) + + t.Run("CreateInvalidGPGKey", func(t *testing.T) { + testCreateInvalidGPGKey(t, tc.makeRequest, tc.token, tc.results[4]) + }) + t.Run("CreateNoneRegistredEmailGPGKey", func(t *testing.T) { + testCreateNoneRegistredEmailGPGKey(t, tc.makeRequest, tc.token, tc.results[5]) + }) + t.Run("CreateValidGPGKey", func(t *testing.T) { + testCreateValidGPGKey(t, tc.makeRequest, tc.token, tc.results[6]) + }) + t.Run("CreateValidSecondaryEmailGPGKeyNotActivated", func(t *testing.T) { + testCreateValidSecondaryEmailGPGKey(t, tc.makeRequest, tc.token, tc.results[7]) + }) + }) + } + + // Check state after basic add + t.Run("CheckState", func(t *testing.T) { + var keys []*api.GPGKey + + req := NewRequest(t, "GET", "/api/v1/user/gpg_keys?token="+token) // GET all keys + resp := session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &keys) + assert.Len(t, keys, 1) + + primaryKey1 := keys[0] // Primary key 1 + assert.EqualValues(t, "38EA3BCED732982C", primaryKey1.KeyID) + assert.Len(t, primaryKey1.Emails, 1) + assert.EqualValues(t, "user2@example.com", primaryKey1.Emails[0].Email) + assert.True(t, primaryKey1.Emails[0].Verified) + + subKey := primaryKey1.SubsKey[0] // Subkey of 38EA3BCED732982C + assert.EqualValues(t, "70D7C694D17D03AD", subKey.KeyID) + assert.Empty(t, subKey.Emails) + + var key api.GPGKey + req = NewRequest(t, "GET", "/api/v1/user/gpg_keys/"+strconv.FormatInt(primaryKey1.ID, 10)+"?token="+token) // Primary key 1 + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &key) + assert.EqualValues(t, "38EA3BCED732982C", key.KeyID) + assert.Len(t, key.Emails, 1) + assert.EqualValues(t, "user2@example.com", key.Emails[0].Email) + assert.True(t, key.Emails[0].Verified) + + req = NewRequest(t, "GET", "/api/v1/user/gpg_keys/"+strconv.FormatInt(subKey.ID, 10)+"?token="+token) // Subkey of 38EA3BCED732982C + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &key) + assert.EqualValues(t, "70D7C694D17D03AD", key.KeyID) + assert.Empty(t, key.Emails) + }) + + // Check state after basic add + t.Run("CheckCommits", func(t *testing.T) { + t.Run("NotSigned", func(t *testing.T) { + var branch api.Branch + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo16/branches/not-signed?token="+token) + resp := session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &branch) + assert.False(t, branch.Commit.Verification.Verified) + }) + + t.Run("SignedWithNotValidatedEmail", func(t *testing.T) { + var branch api.Branch + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo16/branches/good-sign-not-yet-validated?token="+token) + resp := session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &branch) + assert.False(t, branch.Commit.Verification.Verified) + }) + + t.Run("SignedWithValidEmail", func(t *testing.T) { + var branch api.Branch + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo16/branches/good-sign?token="+token) + resp := session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &branch) + assert.True(t, branch.Commit.Verification.Verified) + }) + }) +} + +func testViewOwnGPGKeys(t *testing.T, makeRequest makeRequestFunc, token string, expected int) { + req := NewRequest(t, "GET", "/api/v1/user/gpg_keys?token="+token) + makeRequest(t, req, expected) +} + +func testViewGPGKeys(t *testing.T, makeRequest makeRequestFunc, token string, expected int) { + req := NewRequest(t, "GET", "/api/v1/users/user2/gpg_keys?token="+token) + makeRequest(t, req, expected) +} + +func testGetGPGKey(t *testing.T, makeRequest makeRequestFunc, token string, expected int) { + req := NewRequest(t, "GET", "/api/v1/user/gpg_keys/1?token="+token) + makeRequest(t, req, expected) +} + +func testDeleteGPGKey(t *testing.T, makeRequest makeRequestFunc, token string, expected int) { + req := NewRequest(t, "DELETE", "/api/v1/user/gpg_keys/1?token="+token) + makeRequest(t, req, expected) +} + +func testCreateGPGKey(t *testing.T, makeRequest makeRequestFunc, token string, expected int, publicKey string) { + req := NewRequestWithJSON(t, "POST", "/api/v1/user/gpg_keys?token="+token, api.CreateGPGKeyOption{ + ArmoredKey: publicKey, + }) + makeRequest(t, req, expected) +} + +func testCreateInvalidGPGKey(t *testing.T, makeRequest makeRequestFunc, token string, expected int) { + testCreateGPGKey(t, makeRequest, token, expected, "invalid_key") +} + +func testCreateNoneRegistredEmailGPGKey(t *testing.T, makeRequest makeRequestFunc, token string, expected int) { + testCreateGPGKey(t, makeRequest, token, expected, `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFmGUygBCACjCNbKvMGgp0fd5vyFW9olE1CLCSyyF9gQN2hSuzmZLuAZF2Kh +dCMCG2T1UwzUB/yWUFWJ2BtCwSjuaRv+cGohqEy6bhEBV90peGA33lHfjx7wP25O +7moAphDOTZtDj1AZfCh/PTcJut8Lc0eRDMhNyp/bYtO7SHNT1Hr6rrCV/xEtSAvR +3b148/tmIBiSadaLwc558KU3ucjnW5RVGins3AjBZ+TuT4XXVH/oeLSeXPSJ5rt1 +rHwaseslMqZ4AbvwFLx5qn1OC9rEQv/F548QsA8m0IntLjoPon+6wcubA9Gra21c +Fp6aRYl9x7fiqXDLg8i3s2nKdV7+e6as6Tp9ABEBAAG0FG5vdGtub3duQGV4YW1w +bGUuY29tiQEcBBABAgAGBQJZhlMoAAoJEC8+pvYULDtte/wH/2JNrhmHwDY+hMj0 +batIK4HICnkKxjIgbha80P2Ao08NkzSge58fsxiKDFYAQjHui+ZAw4dq79Ax9AOO +Iv2GS9+DUfWhrb6RF+vNuJldFzcI0rTW/z2q+XGKrUCwN3khJY5XngHfQQrdBtMK +qsoUXz/5B8g422RTbo/SdPsyYAV6HeLLeV3rdgjI1fpaW0seZKHeTXQb/HvNeuPg +qz+XV1g6Gdqa1RjDOaX7A8elVKxrYq3LBtc93FW+grBde8n7JL0zPM3DY+vJ0IJZ +INx/MmBfmtCq05FqNclvU+sj2R3N1JJOtBOjZrJHQbJhzoILou8AkxeX1A+q9OAz +1geiY5E= +=TkP3 +-----END PGP PUBLIC KEY BLOCK-----`) +} + +func testCreateValidGPGKey(t *testing.T, makeRequest makeRequestFunc, token string, expected int) { + // User2 <user2@example.com> //primary & activated + testCreateGPGKey(t, makeRequest, token, expected, `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFmGVsMBCACuxgZ7W7rI9xN08Y4M7B8yx/6/I4Slm94+wXf8YNRvAyqj30dW +VJhyBcnfNRDLKSQp5o/hhfDkCgdqBjLa1PnHlGS3PXJc0hP/FyYPD2BFvNMPpCYS +eu3T1qKSNXm6X0XOWD2LIrdiDC8HaI9FqZVMI/srMK2CF8XCL2m67W1FuoPlWzod +5ORy0IZB7spoF0xihmcgnEGElRmdo5w/vkGH8U7Zyn9Eb57UVFeafgeskf4wqB23 +BjbMdW2YaB+yzMRwYgOnD5lnBD4uqSmvjaV9C0kxn7x+oJkkiRV8/z1cNcO+BaeQ +Akh/yTTeTzYGSc/ZOqCX1O+NOPgSeixVlqenABEBAAG0GVVzZXIyIDx1c2VyMkBl +eGFtcGxlLmNvbT6JAVQEEwEIAD4WIQRXgbSh0TtGbgRd7XI46jvO1zKYLAUCWYZW +wwIbAwUJA8JnAAULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRA46jvO1zKYLF/e +B/91wm2KLMIQBZBA9WA2/+9rQWTo9EqgYrXN60rEzX3cYJWXZiE4DrKR1oWDGNLi +KXOCW62snvJldolBqq0ZqaKvPKzl0Y5TRqbYEc9AjUSqgRin1b+G2DevLGT4ibq+ +7ocQvz0XkASEUAgHahp0Ubiiib1521WwT/duL+AG8Gg0+DK09RfV3eX5/EOkQCKv +8cutqgsd2Smz40A8wXuJkRcipZBtrB/GkUaZ/eJdwEeSYZjEA9GWF61LJT2stvRN +HCk7C3z3pVEek1PluiFs/4VN8BG8yDzW4c0tLty4Fj3VwPqwIbB5AJbquVfhQCb4 +Eep2lm3Lc9b1OwO5N3coPJkouQENBFmGVsMBCADAGba2L6NCOE1i3WIP6CPzbdOo +N3gdTfTgccAx9fNeon9jor+3tgEjlo9/6cXiRoksOV6W4wFab/ZwWgwN6JO4CGvZ +Wi7EQwMMMp1E36YTojKQJrcA9UvMnTHulqQQ88F5E845DhzFQM3erv42QZZMBAX3 +kXCgy1GNFocl6tLUvJdEqs+VcJGGANMpmzE4WLa8KhSYnxipwuQ62JBy9R+cHyKT +OARk8znRqSu5bT3LtlrZ/HXu+6Oy4+2uCdNzZIh5J5tPS7CPA6ptl88iGVBte/CJ +7cjgJWSQqeYp2Y5QvsWAivkQ4Ww9plHbbwV0A2eaHsjjWzlUl3HoJ/snMOhBABEB +AAGJATwEGAEIACYWIQRXgbSh0TtGbgRd7XI46jvO1zKYLAUCWYZWwwIbDAUJA8Jn +AAAKCRA46jvO1zKYLBwLCACQOpeRVrwIKVaWcPMYjVHHJsGscaLKpgpARAUgbiG6 +Cbc2WI8Sm3fRwrY0VAfN+u9QwrtvxANcyB3vTgTzw7FimfhOimxiTSO8HQCfjDZF +Xly8rq+Fua7+ClWUpy21IekW41VvZYjH2sL6EVP+UcEOaGAyN53XfhaRVZPhNtZN +NKAE9N5EG3rbsZ33LzJj40rEKlzFSseAAPft8qA3IXjzFBx+PQXHMpNCagL79he6 +lqockTJ+oPmta4CF/J0U5LUr1tOZXheL3TP6m8d08gDrtn0YuGOPk87i9sJz+jR9 +uy6MA3VSB99SK9ducGmE1Jv8mcziREroz2TEGr0zPs6h +=J59D +-----END PGP PUBLIC KEY BLOCK-----`) +} + +func testCreateValidSecondaryEmailGPGKey(t *testing.T, makeRequest makeRequestFunc, token string, expected int) { + // User2 <user2-2@example.com> //secondary and not activated + testCreateGPGKey(t, makeRequest, token, expected, `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQGNBGC2K2cBDAC1+Xgk+8UfhASVgRngQi4rnQ8k0t+bWsBz4Czd26+cxVDRwlTT +8PALdrbrY/e9iXjcVcZ8Npo4UYe7/LfnL57dc7tgbenRGYYrWyVoNNv58BVw4xCY +RmgvdHWIIPGuz3aME0smHxbJ2KewYTqjTPuVKF/wrHTwCpVWdjYKC5KDo3yx0mro +xf9vOJOnkWNMiEw7TiZfkrbUqxyA53BVsSNKRX5C3b4FJcVT7eiAq7sDAaFxjEHy +ahZslmvg7XZxWzSVzxDNesR7f4xuop8HBjzaluJoVuwiyWculTvz1b6hyHVQr+ad +h8JGjj1tySI65OTFsTuptsfHXjtjl/NR4P6BXkf+FVwweaTQaEzpHkv0m9b9pY43 +CY/8XtS4uNPermiLG/Z0BB1eOCdoOQVHpjOa55IXQWhxXB6NZVyowiUbrR7jLDQy +5JP7D1HmErTR8JRm3VDqGbSaCgugRgFX+lb/fpgFp9k02OeK+JQudolZOt1mVk+T +C4xmEWxfiH15/JMAEQEAAbQbdXNlcjIgPHVzZXIyLTJAZXhhbXBsZS5jb20+iQHU +BBMBCAA+FiEEB/Y4DM3Ba2H9iXmlPO9G70C+/D4FAmC2K2cCGwMFCQPCZwAFCwkI +BwIGFQoJCAsCBBYCAwECHgECF4AACgkQPO9G70C+/D59/Av/XZIhCH4X2FpxCO3d +oCa+sbYkBL5xeUoPfAx5ThXzqL/tllO88TKTMEGZF3k5pocXWH0xmhqlvDTcdb0i +W3O0CN8FLmuotU51c0JC1mt9zwJP9PeJNyqxrMm01Yzj55z/Dz3QHSTlDjrWTWjn +YBqDf2HfdM177oydfSYmevZni1aDmBalWpFPRvqISCO7uFnvg1hJQ5mD/0qie663 +QJ8LAAANg32H9DyPnYi9wU62WX0DMUVTjKctT3cnYCbirjjJ7ZlCCm+cf61CRX1B +E1Ng/Ef3ZcUfXWitZSjfET/pKEMSNjsQawFpZ/LPCBl+UPHzaTPAASeGJvcbZ3py +wZQLQc1MCu2hmMBQ8zHQTdS2Pp0RISxCQLYvVQL6DrcJDNiSqn9p9RQt5c5r5Pjx +80BIPcjj3glOVP7PYE2azQAkt6reEjhimwCfjeDpiPnkBTY7Av2jCcUFhhemDY/j +TRXK1paLphhJ36zC22SeHGxNNakjjuUakqB85DEUeoWuVm6ouQGNBGC2K2cBDADx +G2rIAgMjdPtofhkEZXwv6zdNwmYOlIIM+59bam9Ep/vFq8F5f+xldevm5dvM8SeR +pNwDGSOUf5OKBWBdsJFhlYBl7+EcKd/Tent/XS6JoA9ffF33b+r04L543+ykiKON +WYeYi0F4WwYTIQgqZHJze1sPVkYGR5F0bL8PAcLuwd5dzZVi/q2HakrGdg29N8oY +b/XnoR7FflPrNYdzO6hawi5Inx7KS7aWa0ZkARb0F4HSct+/m6nAZVsoJINLudyQ +ut2NWeU8rWIm1hqyIxQFvuQJy46umq++10J/sWA98bkg41Rx+72+eP7DM5v8IgUp +clJsfljRXIBWbmRAVZvtNI7PX9fwMMhf4M7wHO7G2WV39o1exKps5xFFcn8PUQiX +jCSR81M145CgCdmLUR1y0pdkN/WIqjXBhkPIvO2dxEcodMNHb1aUUuUOnww6+xIP +8rGVw+a2DUiALc8Qr5RP21AYKRctfiwhSQh2KODveMtyLI3U9C/eLRPp+QM3XB8A +EQEAAYkBvAQYAQgAJhYhBAf2OAzNwWth/Yl5pTzvRu9Avvw+BQJgtitnAhsMBQkD +wmcAAAoJEDzvRu9Avvw+3FcMAJBwupyJ4zwQFxTJ5BkDlusG3U2FXEf3bDrXhvNd +qi8eS8Vo/vRiH/w/my5JFpz1o2tJToryF71D+uF5DTItalKquhsQ9reAEmXggqOh +9Jd9mWJIEEWcRORiLNDKENKvE8bouw4U4hRaSF0IaGzAe5mO+oOvwal8L97wFxrZ +4leM1GzkopiuNfbkkBBw2KJcMjYBHzzXSCALnVwhjbgkBEWPIg38APT3cr9KfnMM +q8+tvsGLj4piAl3Lww7+GhSsDOUXH8btR41BSAQDrbO5q6oi/h4nuxoNmQIDW/Ug +s+dd5hnY2FtHRjb4FCR9kAjdTE6stc8wzohWfbg1N+12TTA2ylByAumICVXixavH +RJ7l0OiWJk388qw9mqh3k8HcBxL7OfDlFC9oPmCS0iYiIwW/Yc80kBhoxcvl/Xa7 +mIMMn8taHIaQO7v9ln2EVQYTzbNCmwTw9ovTM0j/Pbkg2EftfP1TCoxQHvBnsCED +6qgtsUdi5eviONRkBgeZtN3oxA== +=MgDv +-----END PGP PUBLIC KEY BLOCK-----`) +} diff --git a/tests/integration/api_helper_for_declarative_test.go b/tests/integration/api_helper_for_declarative_test.go new file mode 100644 index 0000000000..5a798f79f0 --- /dev/null +++ b/tests/integration/api_helper_for_declarative_test.go @@ -0,0 +1,464 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "os" + "testing" + "time" + + "code.gitea.io/gitea/models/perm" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/queue" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/services/forms" + + "github.com/stretchr/testify/assert" +) + +type APITestContext struct { + Reponame string + Session *TestSession + Token string + Username string + ExpectedCode int +} + +func NewAPITestContext(t *testing.T, username, reponame string) APITestContext { + session := loginUser(t, username) + token := getTokenForLoggedInUser(t, session) + return APITestContext{ + Session: session, + Token: token, + Username: username, + Reponame: reponame, + } +} + +func (ctx APITestContext) GitPath() string { + return fmt.Sprintf("%s/%s.git", ctx.Username, ctx.Reponame) +} + +func doAPICreateRepository(ctx APITestContext, empty bool, callback ...func(*testing.T, api.Repository)) func(*testing.T) { + return func(t *testing.T) { + createRepoOption := &api.CreateRepoOption{ + AutoInit: !empty, + Description: "Temporary repo", + Name: ctx.Reponame, + Private: true, + Template: true, + Gitignores: "", + License: "WTFPL", + Readme: "Default", + } + req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos?token="+ctx.Token, createRepoOption) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) + + var repository api.Repository + DecodeJSON(t, resp, &repository) + if len(callback) > 0 { + callback[0](t, repository) + } + } +} + +func doAPIEditRepository(ctx APITestContext, editRepoOption *api.EditRepoOption, callback ...func(*testing.T, api.Repository)) func(*testing.T) { + return func(t *testing.T) { + req := NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), ctx.Token), editRepoOption) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + resp := ctx.Session.MakeRequest(t, req, http.StatusOK) + + var repository api.Repository + DecodeJSON(t, resp, &repository) + if len(callback) > 0 { + callback[0](t, repository) + } + } +} + +func doAPIAddCollaborator(ctx APITestContext, username string, mode perm.AccessMode) func(*testing.T) { + return func(t *testing.T) { + permission := "read" + + if mode == perm.AccessModeAdmin { + permission = "admin" + } else if mode > perm.AccessModeRead { + permission = "write" + } + addCollaboratorOption := &api.AddCollaboratorOption{ + Permission: &permission, + } + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/collaborators/%s?token=%s", ctx.Username, ctx.Reponame, username, ctx.Token), addCollaboratorOption) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + ctx.Session.MakeRequest(t, req, http.StatusNoContent) + } +} + +func doAPIForkRepository(ctx APITestContext, username string, callback ...func(*testing.T, api.Repository)) func(*testing.T) { + return func(t *testing.T) { + createForkOption := &api.CreateForkOption{} + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks?token=%s", username, ctx.Reponame, ctx.Token), createForkOption) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + resp := ctx.Session.MakeRequest(t, req, http.StatusAccepted) + var repository api.Repository + DecodeJSON(t, resp, &repository) + if len(callback) > 0 { + callback[0](t, repository) + } + } +} + +func doAPIGetRepository(ctx APITestContext, callback ...func(*testing.T, api.Repository)) func(*testing.T) { + return func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", ctx.Username, ctx.Reponame, ctx.Token) + + req := NewRequest(t, "GET", urlStr) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + resp := ctx.Session.MakeRequest(t, req, http.StatusOK) + + var repository api.Repository + DecodeJSON(t, resp, &repository) + if len(callback) > 0 { + callback[0](t, repository) + } + } +} + +func doAPIDeleteRepository(ctx APITestContext) func(*testing.T) { + return func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", ctx.Username, ctx.Reponame, ctx.Token) + + req := NewRequest(t, "DELETE", urlStr) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + ctx.Session.MakeRequest(t, req, http.StatusNoContent) + } +} + +func doAPICreateUserKey(ctx APITestContext, keyname, keyFile string, callback ...func(*testing.T, api.PublicKey)) func(*testing.T) { + return func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/user/keys?token=%s", ctx.Token) + + dataPubKey, err := os.ReadFile(keyFile + ".pub") + assert.NoError(t, err) + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateKeyOption{ + Title: keyname, + Key: string(dataPubKey), + }) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) + var publicKey api.PublicKey + DecodeJSON(t, resp, &publicKey) + if len(callback) > 0 { + callback[0](t, publicKey) + } + } +} + +func doAPIDeleteUserKey(ctx APITestContext, keyID int64) func(*testing.T) { + return func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/user/keys/%d?token=%s", keyID, ctx.Token) + + req := NewRequest(t, "DELETE", urlStr) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + ctx.Session.MakeRequest(t, req, http.StatusNoContent) + } +} + +func doAPICreateDeployKey(ctx APITestContext, keyname, keyFile string, readOnly bool) func(*testing.T) { + return func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/keys?token=%s", ctx.Username, ctx.Reponame, ctx.Token) + + dataPubKey, err := os.ReadFile(keyFile + ".pub") + assert.NoError(t, err) + req := NewRequestWithJSON(t, "POST", urlStr, api.CreateKeyOption{ + Title: keyname, + Key: string(dataPubKey), + ReadOnly: readOnly, + }) + + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + ctx.Session.MakeRequest(t, req, http.StatusCreated) + } +} + +func doAPICreatePullRequest(ctx APITestContext, owner, repo, baseBranch, headBranch string) func(*testing.T) (api.PullRequest, error) { + return func(t *testing.T) (api.PullRequest, error) { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", + owner, repo, ctx.Token) + req := NewRequestWithJSON(t, http.MethodPost, urlStr, &api.CreatePullRequestOption{ + Head: headBranch, + Base: baseBranch, + Title: fmt.Sprintf("create a pr from %s to %s", headBranch, baseBranch), + }) + + expected := http.StatusCreated + if ctx.ExpectedCode != 0 { + expected = ctx.ExpectedCode + } + resp := ctx.Session.MakeRequest(t, req, expected) + + decoder := json.NewDecoder(resp.Body) + pr := api.PullRequest{} + err := decoder.Decode(&pr) + return pr, err + } +} + +func doAPIGetPullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) (api.PullRequest, error) { + return func(t *testing.T) (api.PullRequest, error) { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d?token=%s", + owner, repo, index, ctx.Token) + req := NewRequest(t, http.MethodGet, urlStr) + + expected := http.StatusOK + if ctx.ExpectedCode != 0 { + expected = ctx.ExpectedCode + } + resp := ctx.Session.MakeRequest(t, req, expected) + + decoder := json.NewDecoder(resp.Body) + pr := api.PullRequest{} + err := decoder.Decode(&pr) + return pr, err + } +} + +func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) { + return func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s", + owner, repo, index, ctx.Token) + + var req *http.Request + var resp *httptest.ResponseRecorder + + for i := 0; i < 6; i++ { + req = NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{ + MergeMessageField: "doAPIMergePullRequest Merge", + Do: string(repo_model.MergeStyleMerge), + }) + + resp = ctx.Session.MakeRequest(t, req, NoExpectedStatus) + + if resp.Code != http.StatusMethodNotAllowed { + break + } + err := api.APIError{} + DecodeJSON(t, resp, &err) + assert.EqualValues(t, "Please try again later", err.Message) + queue.GetManager().FlushAll(context.Background(), 5*time.Second) + <-time.After(1 * time.Second) + } + + expected := ctx.ExpectedCode + if expected == 0 { + expected = http.StatusOK + } + + if !assert.EqualValues(t, expected, resp.Code, + "Request: %s %s", req.Method, req.URL.String()) { + logUnexpectedResponse(t, resp) + } + } +} + +func doAPIManuallyMergePullRequest(ctx APITestContext, owner, repo, commitID string, index int64) func(*testing.T) { + return func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s", + owner, repo, index, ctx.Token) + req := NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{ + Do: string(repo_model.MergeStyleManuallyMerged), + MergeCommitID: commitID, + }) + + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + ctx.Session.MakeRequest(t, req, http.StatusOK) + } +} + +func doAPIAutoMergePullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) { + return func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s", + owner, repo, index, ctx.Token) + req := NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{ + MergeMessageField: "doAPIMergePullRequest Merge", + Do: string(repo_model.MergeStyleMerge), + MergeWhenChecksSucceed: true, + }) + + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + ctx.Session.MakeRequest(t, req, 200) + } +} + +func doAPICancelAutoMergePullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) { + return func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s", + owner, repo, index, ctx.Token) + req := NewRequest(t, http.MethodDelete, urlStr) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + ctx.Session.MakeRequest(t, req, 204) + } +} + +func doAPIGetBranch(ctx APITestContext, branch string, callback ...func(*testing.T, api.Branch)) func(*testing.T) { + return func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/branches/%s?token=%s", ctx.Username, ctx.Reponame, branch, ctx.Token) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + resp := ctx.Session.MakeRequest(t, req, http.StatusOK) + + var branch api.Branch + DecodeJSON(t, resp, &branch) + if len(callback) > 0 { + callback[0](t, branch) + } + } +} + +func doAPICreateFile(ctx APITestContext, treepath string, options *api.CreateFileOptions, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) { + return func(t *testing.T) { + url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", ctx.Username, ctx.Reponame, treepath, ctx.Token) + req := NewRequestWithJSON(t, "POST", url, &options) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) + + var contents api.FileResponse + DecodeJSON(t, resp, &contents) + if len(callback) > 0 { + callback[0](t, contents) + } + } +} + +func doAPICreateOrganization(ctx APITestContext, options *api.CreateOrgOption, callback ...func(*testing.T, api.Organization)) func(t *testing.T) { + return func(t *testing.T) { + url := fmt.Sprintf("/api/v1/orgs?token=%s", ctx.Token) + + req := NewRequestWithJSON(t, "POST", url, &options) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) + + var contents api.Organization + DecodeJSON(t, resp, &contents) + if len(callback) > 0 { + callback[0](t, contents) + } + } +} + +func doAPICreateOrganizationRepository(ctx APITestContext, orgName string, options *api.CreateRepoOption, callback ...func(*testing.T, api.Repository)) func(t *testing.T) { + return func(t *testing.T) { + url := fmt.Sprintf("/api/v1/orgs/%s/repos?token=%s", orgName, ctx.Token) + + req := NewRequestWithJSON(t, "POST", url, &options) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) + + var contents api.Repository + DecodeJSON(t, resp, &contents) + if len(callback) > 0 { + callback[0](t, contents) + } + } +} + +func doAPICreateOrganizationTeam(ctx APITestContext, orgName string, options *api.CreateTeamOption, callback ...func(*testing.T, api.Team)) func(t *testing.T) { + return func(t *testing.T) { + url := fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", orgName, ctx.Token) + + req := NewRequestWithJSON(t, "POST", url, &options) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) + + var contents api.Team + DecodeJSON(t, resp, &contents) + if len(callback) > 0 { + callback[0](t, contents) + } + } +} + +func doAPIAddUserToOrganizationTeam(ctx APITestContext, teamID int64, username string) func(t *testing.T) { + return func(t *testing.T) { + url := fmt.Sprintf("/api/v1/teams/%d/members/%s?token=%s", teamID, username, ctx.Token) + + req := NewRequest(t, "PUT", url) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + ctx.Session.MakeRequest(t, req, http.StatusNoContent) + } +} + +func doAPIAddRepoToOrganizationTeam(ctx APITestContext, teamID int64, orgName, repoName string) func(t *testing.T) { + return func(t *testing.T) { + url := fmt.Sprintf("/api/v1/teams/%d/repos/%s/%s?token=%s", teamID, orgName, repoName, ctx.Token) + + req := NewRequest(t, "PUT", url) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + ctx.Session.MakeRequest(t, req, http.StatusNoContent) + } +} diff --git a/tests/integration/api_httpsig_test.go b/tests/integration/api_httpsig_test.go new file mode 100644 index 0000000000..80b3c586b4 --- /dev/null +++ b/tests/integration/api_httpsig_test.go @@ -0,0 +1,138 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "encoding/base64" + "fmt" + "net/http" + "net/url" + "testing" + + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/go-fed/httpsig" + "golang.org/x/crypto/ssh" +) + +const ( + httpsigPrivateKey = `-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAqjmQeb5Eb1xV7qbNf9ErQ0XRvKZWzUsLFhJzZz+Ab7q8WtPs91vQ +fBiypw4i8OTG6WzDcgZaV8Ndxn7iHnIstdA1k89MVG4stydymmwmk9+mrCMNsu5OmdIy9F +AZ61RDcKuf5VG2WKkmeK0VO+OMJIYfE1C6czNeJ6UAmcIOmhGxvjMI83XUO9n0ftwTwayp ++XU5prvKx/fTvlPjbraPNU4OzwPjVLqXBzpoXYhBquPaZYFRVyvfFZLObYsmy+BrsxcloM +l+9w4P0ATJ9njB7dRDL+RrN4uhhYSihqOK4w4vaiOj1+aA0eC0zXunEfLXfGIVQ/FhWcCy +5f72mMiKnQAAA9AxSmzFMUpsxQAAAAdzc2gtcnNhAAABAQCqOZB5vkRvXFXups1/0StDRd +G8plbNSwsWEnNnP4Bvurxa0+z3W9B8GLKnDiLw5MbpbMNyBlpXw13GfuIeciy10DWTz0xU +biy3J3KabCaT36asIw2y7k6Z0jL0UBnrVENwq5/lUbZYqSZ4rRU744wkhh8TULpzM14npQ +CZwg6aEbG+MwjzddQ72fR+3BPBrKn5dTmmu8rH99O+U+Nuto81Tg7PA+NUupcHOmhdiEGq +49plgVFXK98Vks5tiybL4GuzFyWgyX73Dg/QBMn2eMHt1EMv5Gs3i6GFhKKGo4rjDi9qI6 +PX5oDR4LTNe6cR8td8YhVD8WFZwLLl/vaYyIqdAAAAAwEAAQAAAQBz+nyBNi2SYir6SxPA +flcnoq5gBkUl4ndPNosCUbXEakpi5/mQHzJRGtK+F1efIYCVEdGoIsPy/90onNKbQ9dKmO +2oI5kx/U7iCzJ+HCm8nqkEp21x+AP9scWdx+Wg/OxmG8j5iU7f4X+gwOyyvTqCuA78Lgia +7Oi9wiJCoIEqXr6dRYGJzfASwKA2dj995HzATexleLSD5fQCmZTF+Vh5OQ5WmE+c53JdZS +T3Plie/P/smgSWBtf1fWr6JL2+EBsqQsIK1Jo7r/7rxsz+ILoVfnneNQY4QSa9W+t6ZAI+ +caSA0Guv7vC92ewjlMVlwKa3XaEjMJb5sFlg1r6TYMwBAAAAgQDQwXvgSXNaSHIeH53/Ab +t4BlNibtxK8vY8CZFloAKXkjrivKSlDAmQCM0twXOweX2ScPjE+XlSMV4AUsv/J6XHGHci +W3+PGIBfc/fQRBpiyhzkoXYDVrlkSKHffCnAqTUQlYkhr0s7NkZpEeqPE0doAUs4dK3Iqb +zdtz8e5BPXZwAAAIEA4U/JskIu5Oge8Is2OLOhlol0EJGw5JGodpFyhbMC+QYK9nYqy7wI +a6mZ2EfOjjwIZD/+wYyulw6cRve4zXwgzUEXLIKp8/H3sYvJK2UMeP7y68sQFqGxbm6Rnh +tyBBSaJQnOXVOFf9gqZGCyO/J0Illg3AXTuC8KS/cxwasC38EAAACBAMFo/6XQoR6E3ynj +VBaz2SilWqQBixUyvcNz8LY73IIDCecoccRMFSEKhWtvlJijxvFbF9M8g9oKAVPuub4V5r +CGmwVPEd5yt4C2iyV0PhLp1PA2/i42FpCSnHaz/EXSz6ncTZcOMMuDqUbgUUpQg4VSUDl9 +fhTNAzWwZoQ91aHdAAAAFHUwMDIyMTQ2QGljdHMtcC1ueC03AQIDBAUG +-----END OPENSSH PRIVATE KEY-----` + httpsigCertificate = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgiR7SU8gmZLhopx4Y03nOXVuAb+4fyMcJYjMGcE1Z2oEAAAADAQABAAABAQCqOZB5vkRvXFXups1/0StDRdG8plbNSwsWEnNnP4Bvurxa0+z3W9B8GLKnDiLw5MbpbMNyBlpXw13GfuIeciy10DWTz0xUbiy3J3KabCaT36asIw2y7k6Z0jL0UBnrVENwq5/lUbZYqSZ4rRU744wkhh8TULpzM14npQCZwg6aEbG+MwjzddQ72fR+3BPBrKn5dTmmu8rH99O+U+Nuto81Tg7PA+NUupcHOmhdiEGq49plgVFXK98Vks5tiybL4GuzFyWgyX73Dg/QBMn2eMHt1EMv5Gs3i6GFhKKGo4rjDi9qI6PX5oDR4LTNe6cR8td8YhVD8WFZwLLl/vaYyIqdAAAAAAAAAAEAAAABAAAABXVzZXIxAAAACQAAAAV1c2VyMQAAAABimoIOAAAAAMCWkRMAAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAABlwAAAAdzc2gtcnNhAAAAAwEAAQAAAYEAm+AwtXTBZyeqV1qOxjMU3Ibc5iR2M3zerGfRQDxUeIozC3xpIvqJbzjDuRapdf8hpxn2xC0GtUusuLIUr4/+Svs1BUnJhF2H9xnK/O0aopS5MpNekUvnBzQdbvO8Ux2xE2mt58giXhkEaXeCEODSqG++OZsA2e40AR/AGRJ4OdDofMvH4vLJAQQc2mKdYpYL8xu+NC+7nsenx1etpsqtEl3gmvqCVI6t9uhVPMvlbGt9h/AN3u7ToF2T3bdk1TZbcdkvR9ljvETIuy32ksAETX8tc7vm30edK+nn/GMeWCgjM+MFm9Uh1NRkvNNJozo5SJy0DkWETTJUsEdfry5VQ3IjqhWqQ0m4/mDlTmsEdEdWqpUiqWZLd9w7jgT8fanuglZyIu2fj8fyqjPjiws5S2P0Uvi28UKQ1nH01UYj/kuakU3BNzN1IqDf3tARP9fjKV/dCBqb1ZAOtyC2GyhGuGzNwEi+woUwq+sTeV0/hqVSb3hSitXHzcfRMRyOK82BAAABlAAAAAxyc2Etc2hhMi01MTIAAAGAMBfgZFvz4BdxriGKYd6eRhMo6hf+I8S9uzNRsflJXHuA+HR9ExIm/Q9JjKmfThQzNyGGBOBILaDU205SAJuG+kk3SieSQDd75ZQd8YmNlCc+516AriOsTiyVCupnf3I2euTjMZqEZbJcBbkBljppTOWQVN7xxE8QakDfGhg0+RjJE9wYOTmkKpDBfII5Nw8V5DoOD7kNEpXYqHdy/8lVxpqUYNIP1J0dNP4f6qBcZcM1PDA12q8zwIGqSNNjf2UXY/Nr8nv9CnK4fB8NDOPKTBa4cm48BGbvM/X0l6dYKswuZ9Np8lw+y6+GxTgznGCrkzMmuEV4FzSq4xHp41H2L2MTwUkwYaeyG1VP6aWkvn6zPkSxaaJDfQX7CAFe17IhIGXR0UPLjKjh35nDLzMWb/W6/W1lK9YkZNHXSf7Z9m9MUAZN7yQgOggGsuYEW4imZxvZizMd+fdDu9mbhr0FDis89I7MSJDnyYRE9FXS7p3QpppBwGcss/9yV3JV3Bjc` +) + +func TestHTTPSigPubKey(t *testing.T) { + // Add our public key to user1 + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user1") + token := url.QueryEscape(getTokenForLoggedInUser(t, session)) + keysURL := fmt.Sprintf("/api/v1/user/keys?token=%s", token) + keyType := "ssh-rsa" + keyContent := "AAAAB3NzaC1yc2EAAAADAQABAAABAQCqOZB5vkRvXFXups1/0StDRdG8plbNSwsWEnNnP4Bvurxa0+z3W9B8GLKnDiLw5MbpbMNyBlpXw13GfuIeciy10DWTz0xUbiy3J3KabCaT36asIw2y7k6Z0jL0UBnrVENwq5/lUbZYqSZ4rRU744wkhh8TULpzM14npQCZwg6aEbG+MwjzddQ72fR+3BPBrKn5dTmmu8rH99O+U+Nuto81Tg7PA+NUupcHOmhdiEGq49plgVFXK98Vks5tiybL4GuzFyWgyX73Dg/QBMn2eMHt1EMv5Gs3i6GFhKKGo4rjDi9qI6PX5oDR4LTNe6cR8td8YhVD8WFZwLLl/vaYyIqd" + rawKeyBody := api.CreateKeyOption{ + Title: "test-key", + Key: keyType + " " + keyContent, + } + req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody) + session.MakeRequest(t, req, http.StatusCreated) + + // parse our private key and create the httpsig request + sshSigner, _ := ssh.ParsePrivateKey([]byte(httpsigPrivateKey)) + keyID := ssh.FingerprintSHA256(sshSigner.PublicKey()) + + // create the request + req = NewRequest(t, "GET", "/api/v1/admin/users") + + signer, _, err := httpsig.NewSSHSigner(sshSigner, httpsig.DigestSha512, []string{httpsig.RequestTarget, "(created)", "(expires)"}, httpsig.Signature, 10) + if err != nil { + t.Fatal(err) + } + + // sign the request + err = signer.SignRequest(keyID, req, nil) + if err != nil { + t.Fatal(err) + } + + // make the request + MakeRequest(t, req, http.StatusOK) +} + +func TestHTTPSigCert(t *testing.T) { + // Add our public key to user1 + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user1") + + csrf := GetCSRF(t, session, "/user/settings/keys") + req := NewRequestWithValues(t, "POST", "/user/settings/keys", map[string]string{ + "_csrf": csrf, + "content": "user1", + "title": "principal", + "type": "principal", + }) + + session.MakeRequest(t, req, http.StatusSeeOther) + pkcert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(httpsigCertificate)) + if err != nil { + t.Fatal(err) + } + + // parse our private key and create the httpsig request + sshSigner, _ := ssh.ParsePrivateKey([]byte(httpsigPrivateKey)) + keyID := "gitea" + + // create our certificate signer using the ssh signer and our certificate + certSigner, err := ssh.NewCertSigner(pkcert.(*ssh.Certificate), sshSigner) + if err != nil { + t.Fatal(err) + } + + // create the request + req = NewRequest(t, "GET", "/api/v1/admin/users") + + // add our cert to the request + certString := base64.RawStdEncoding.EncodeToString(pkcert.(*ssh.Certificate).Marshal()) + req.Header.Add("x-ssh-certificate", certString) + + signer, _, err := httpsig.NewSSHSigner(certSigner, httpsig.DigestSha512, []string{httpsig.RequestTarget, "(created)", "(expires)", "x-ssh-certificate"}, httpsig.Signature, 10) + if err != nil { + t.Fatal(err) + } + + // sign the request + err = signer.SignRequest(keyID, req, nil) + if err != nil { + t.Fatal(err) + } + + // make the request + MakeRequest(t, req, http.StatusOK) +} diff --git a/tests/integration/api_issue_label_test.go b/tests/integration/api_issue_label_test.go new file mode 100644 index 0000000000..586c50a55f --- /dev/null +++ b/tests/integration/api_issue_label_test.go @@ -0,0 +1,208 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "strings" + "testing" + + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func TestAPIModifyLabels(t *testing.T) { + assert.NoError(t, unittest.LoadFixtures()) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels?token=%s", owner.Name, repo.Name, token) + + // CreateLabel + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ + Name: "TestL 1", + Color: "abcdef", + Description: "test label", + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + apiLabel := new(api.Label) + DecodeJSON(t, resp, &apiLabel) + dbLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: apiLabel.ID, RepoID: repo.ID}) + assert.EqualValues(t, dbLabel.Name, apiLabel.Name) + assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + + req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ + Name: "TestL 2", + Color: "#123456", + Description: "jet another test label", + }) + session.MakeRequest(t, req, http.StatusCreated) + req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ + Name: "WrongTestL", + Color: "#12345g", + }) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + // ListLabels + req = NewRequest(t, "GET", urlStr) + resp = session.MakeRequest(t, req, http.StatusOK) + var apiLabels []*api.Label + DecodeJSON(t, resp, &apiLabels) + assert.Len(t, apiLabels, 2) + + // GetLabel + singleURLStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels/%d?token=%s", owner.Name, repo.Name, dbLabel.ID, token) + req = NewRequest(t, "GET", singleURLStr) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiLabel) + assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + + // EditLabel + newName := "LabelNewName" + newColor := "09876a" + newColorWrong := "09g76a" + req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ + Name: &newName, + Color: &newColor, + }) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiLabel) + assert.EqualValues(t, newColor, apiLabel.Color) + req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ + Color: &newColorWrong, + }) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + // DeleteLabel + req = NewRequest(t, "DELETE", singleURLStr) + session.MakeRequest(t, req, http.StatusNoContent) +} + +func TestAPIAddIssueLabels(t *testing.T) { + assert.NoError(t, unittest.LoadFixtures()) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID}) + _ = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{RepoID: repo.ID, ID: 2}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels?token=%s", + repo.OwnerName, repo.Name, issue.Index, token) + req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueLabelsOption{ + Labels: []int64{1, 2}, + }) + resp := session.MakeRequest(t, req, http.StatusOK) + var apiLabels []*api.Label + DecodeJSON(t, resp, &apiLabels) + assert.Len(t, apiLabels, unittest.GetCount(t, &issues_model.IssueLabel{IssueID: issue.ID})) + + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: 2}) +} + +func TestAPIReplaceIssueLabels(t *testing.T) { + assert.NoError(t, unittest.LoadFixtures()) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID}) + label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{RepoID: repo.ID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels?token=%s", + owner.Name, repo.Name, issue.Index, token) + req := NewRequestWithJSON(t, "PUT", urlStr, &api.IssueLabelsOption{ + Labels: []int64{label.ID}, + }) + resp := session.MakeRequest(t, req, http.StatusOK) + var apiLabels []*api.Label + DecodeJSON(t, resp, &apiLabels) + if assert.Len(t, apiLabels, 1) { + assert.EqualValues(t, label.ID, apiLabels[0].ID) + } + + unittest.AssertCount(t, &issues_model.IssueLabel{IssueID: issue.ID}, 1) + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label.ID}) +} + +func TestAPIModifyOrgLabels(t *testing.T) { + assert.NoError(t, unittest.LoadFixtures()) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + user := "user1" + session := loginUser(t, user) + token := getTokenForLoggedInUser(t, session) + urlStr := fmt.Sprintf("/api/v1/orgs/%s/labels?token=%s", owner.Name, token) + + // CreateLabel + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ + Name: "TestL 1", + Color: "abcdef", + Description: "test label", + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + apiLabel := new(api.Label) + DecodeJSON(t, resp, &apiLabel) + dbLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: apiLabel.ID, OrgID: owner.ID}) + assert.EqualValues(t, dbLabel.Name, apiLabel.Name) + assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + + req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ + Name: "TestL 2", + Color: "#123456", + Description: "jet another test label", + }) + session.MakeRequest(t, req, http.StatusCreated) + req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ + Name: "WrongTestL", + Color: "#12345g", + }) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + // ListLabels + req = NewRequest(t, "GET", urlStr) + resp = session.MakeRequest(t, req, http.StatusOK) + var apiLabels []*api.Label + DecodeJSON(t, resp, &apiLabels) + assert.Len(t, apiLabels, 4) + + // GetLabel + singleURLStr := fmt.Sprintf("/api/v1/orgs/%s/labels/%d?token=%s", owner.Name, dbLabel.ID, token) + req = NewRequest(t, "GET", singleURLStr) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiLabel) + assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + + // EditLabel + newName := "LabelNewName" + newColor := "09876a" + newColorWrong := "09g76a" + req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ + Name: &newName, + Color: &newColor, + }) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiLabel) + assert.EqualValues(t, newColor, apiLabel.Color) + req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ + Color: &newColorWrong, + }) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + // DeleteLabel + req = NewRequest(t, "DELETE", singleURLStr) + session.MakeRequest(t, req, http.StatusNoContent) +} diff --git a/tests/integration/api_issue_milestone_test.go b/tests/integration/api_issue_milestone_test.go new file mode 100644 index 0000000000..e22a091bb8 --- /dev/null +++ b/tests/integration/api_issue_milestone_test.go @@ -0,0 +1,81 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIIssuesMilestone(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: milestone.RepoID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + assert.Equal(t, int64(1), int64(milestone.NumIssues)) + assert.Equal(t, structs.StateOpen, milestone.State()) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + + // update values of issue + milestoneState := "closed" + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%d?token=%s", owner.Name, repo.Name, milestone.ID, token) + req := NewRequestWithJSON(t, "PATCH", urlStr, structs.EditMilestoneOption{ + State: &milestoneState, + }) + resp := session.MakeRequest(t, req, http.StatusOK) + var apiMilestone structs.Milestone + DecodeJSON(t, resp, &apiMilestone) + assert.EqualValues(t, "closed", apiMilestone.State) + + req = NewRequest(t, "GET", urlStr) + resp = session.MakeRequest(t, req, http.StatusOK) + var apiMilestone2 structs.Milestone + DecodeJSON(t, resp, &apiMilestone2) + assert.EqualValues(t, "closed", apiMilestone2.State) + + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/milestones?token=%s", owner.Name, repo.Name, token), structs.CreateMilestoneOption{ + Title: "wow", + Description: "closed one", + State: "closed", + }) + resp = session.MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, &apiMilestone) + assert.Equal(t, "wow", apiMilestone.Title) + assert.Equal(t, structs.StateClosed, apiMilestone.State) + + var apiMilestones []structs.Milestone + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/milestones?state=%s&token=%s", owner.Name, repo.Name, "all", token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiMilestones) + assert.Len(t, apiMilestones, 4) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%s?token=%s", owner.Name, repo.Name, apiMilestones[2].Title, token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiMilestone) + assert.EqualValues(t, apiMilestones[2], apiMilestone) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/milestones?state=%s&name=%s&token=%s", owner.Name, repo.Name, "all", "milestone2", token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiMilestones) + assert.Len(t, apiMilestones, 1) + assert.Equal(t, int64(2), apiMilestones[0].ID) + + req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%d?token=%s", owner.Name, repo.Name, apiMilestone.ID, token)) + session.MakeRequest(t, req, http.StatusNoContent) +} diff --git a/tests/integration/api_issue_reaction_test.go b/tests/integration/api_issue_reaction_test.go new file mode 100644 index 0000000000..a3cb9303fb --- /dev/null +++ b/tests/integration/api_issue_reaction_test.go @@ -0,0 +1,144 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + "time" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/convert" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIIssuesReactions(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + _ = issue.LoadRepo(db.DefaultContext) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions?token=%s", + owner.Name, issue.Repo.Name, issue.Index, token) + + // Try to add not allowed reaction + req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "wrong", + }) + session.MakeRequest(t, req, http.StatusForbidden) + + // Delete not allowed reaction + req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ + Reaction: "zzz", + }) + session.MakeRequest(t, req, http.StatusOK) + + // Add allowed reaction + req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "rocket", + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + var apiNewReaction api.Reaction + DecodeJSON(t, resp, &apiNewReaction) + + // Add existing reaction + session.MakeRequest(t, req, http.StatusForbidden) + + // Get end result of reaction list of issue #1 + req = NewRequestf(t, "GET", urlStr) + resp = session.MakeRequest(t, req, http.StatusOK) + var apiReactions []*api.Reaction + DecodeJSON(t, resp, &apiReactions) + expectResponse := make(map[int]api.Reaction) + expectResponse[0] = api.Reaction{ + User: convert.ToUser(user2, user2), + Reaction: "eyes", + Created: time.Unix(1573248003, 0), + } + expectResponse[1] = apiNewReaction + assert.Len(t, apiReactions, 2) + for i, r := range apiReactions { + assert.Equal(t, expectResponse[i].Reaction, r.Reaction) + assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix()) + assert.Equal(t, expectResponse[i].User.ID, r.User.ID) + } +} + +func TestAPICommentReactions(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) + _ = comment.LoadIssue() + issue := comment.Issue + _ = issue.LoadRepo(db.DefaultContext) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions?token=%s", + owner.Name, issue.Repo.Name, comment.ID, token) + + // Try to add not allowed reaction + req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "wrong", + }) + session.MakeRequest(t, req, http.StatusForbidden) + + // Delete none existing reaction + req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ + Reaction: "eyes", + }) + session.MakeRequest(t, req, http.StatusOK) + + // Add allowed reaction + req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "+1", + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + var apiNewReaction api.Reaction + DecodeJSON(t, resp, &apiNewReaction) + + // Add existing reaction + session.MakeRequest(t, req, http.StatusForbidden) + + // Get end result of reaction list of issue #1 + req = NewRequestf(t, "GET", urlStr) + resp = session.MakeRequest(t, req, http.StatusOK) + var apiReactions []*api.Reaction + DecodeJSON(t, resp, &apiReactions) + expectResponse := make(map[int]api.Reaction) + expectResponse[0] = api.Reaction{ + User: convert.ToUser(user2, user2), + Reaction: "laugh", + Created: time.Unix(1573248004, 0), + } + expectResponse[1] = api.Reaction{ + User: convert.ToUser(user1, user1), + Reaction: "laugh", + Created: time.Unix(1573248005, 0), + } + expectResponse[2] = apiNewReaction + assert.Len(t, apiReactions, 3) + for i, r := range apiReactions { + assert.Equal(t, expectResponse[i].Reaction, r.Reaction) + assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix()) + assert.Equal(t, expectResponse[i].User.ID, r.User.ID) + } +} diff --git a/tests/integration/api_issue_stopwatch_test.go b/tests/integration/api_issue_stopwatch_test.go new file mode 100644 index 0000000000..c2ad9c45e8 --- /dev/null +++ b/tests/integration/api_issue_stopwatch_test.go @@ -0,0 +1,92 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIListStopWatches(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "GET", "/api/v1/user/stopwatches?token=%s", token) + resp := session.MakeRequest(t, req, http.StatusOK) + var apiWatches []*api.StopWatch + DecodeJSON(t, resp, &apiWatches) + stopwatch := unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: owner.ID}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: stopwatch.IssueID}) + if assert.Len(t, apiWatches, 1) { + assert.EqualValues(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix()) + assert.EqualValues(t, issue.Index, apiWatches[0].IssueIndex) + assert.EqualValues(t, issue.Title, apiWatches[0].IssueTitle) + assert.EqualValues(t, repo.Name, apiWatches[0].RepoName) + assert.EqualValues(t, repo.OwnerName, apiWatches[0].RepoOwnerName) + assert.Greater(t, apiWatches[0].Seconds, int64(0)) + } +} + +func TestAPIStopStopWatches(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + _ = issue.LoadRepo(db.DefaultContext) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/issues/%d/stopwatch/stop?token=%s", owner.Name, issue.Repo.Name, issue.Index, token) + session.MakeRequest(t, req, http.StatusCreated) + session.MakeRequest(t, req, http.StatusConflict) +} + +func TestAPICancelStopWatches(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + _ = issue.LoadRepo(db.DefaultContext) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/%d/stopwatch/delete?token=%s", owner.Name, issue.Repo.Name, issue.Index, token) + session.MakeRequest(t, req, http.StatusNoContent) + session.MakeRequest(t, req, http.StatusConflict) +} + +func TestAPIStartStopWatches(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3}) + _ = issue.LoadRepo(db.DefaultContext) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/issues/%d/stopwatch/start?token=%s", owner.Name, issue.Repo.Name, issue.Index, token) + session.MakeRequest(t, req, http.StatusCreated) + session.MakeRequest(t, req, http.StatusConflict) +} diff --git a/tests/integration/api_issue_subscription_test.go b/tests/integration/api_issue_subscription_test.go new file mode 100644 index 0000000000..f4588fbbc4 --- /dev/null +++ b/tests/integration/api_issue_subscription_test.go @@ -0,0 +1,77 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIIssueSubscriptions(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + issue1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + issue3 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3}) + issue4 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}) + issue5 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 8}) + + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue1.PosterID}) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + + testSubscription := func(issue *issues_model.Issue, isWatching bool) { + issueRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/check?token=%s", issueRepo.OwnerName, issueRepo.Name, issue.Index, token) + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + wi := new(api.WatchInfo) + DecodeJSON(t, resp, wi) + + assert.EqualValues(t, isWatching, wi.Subscribed) + assert.EqualValues(t, !isWatching, wi.Ignored) + assert.EqualValues(t, issue.APIURL()+"/subscriptions", wi.URL) + assert.EqualValues(t, issue.CreatedUnix, wi.CreatedAt.Unix()) + assert.EqualValues(t, issueRepo.APIURL(), wi.RepositoryURL) + } + + testSubscription(issue1, true) + testSubscription(issue2, true) + testSubscription(issue3, true) + testSubscription(issue4, false) + testSubscription(issue5, false) + + issue1Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue1.RepoID}) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/%s?token=%s", issue1Repo.OwnerName, issue1Repo.Name, issue1.Index, owner.Name, token) + req := NewRequest(t, "DELETE", urlStr) + session.MakeRequest(t, req, http.StatusCreated) + testSubscription(issue1, false) + + req = NewRequest(t, "DELETE", urlStr) + session.MakeRequest(t, req, http.StatusOK) + testSubscription(issue1, false) + + issue5Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue5.RepoID}) + urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/%s?token=%s", issue5Repo.OwnerName, issue5Repo.Name, issue5.Index, owner.Name, token) + req = NewRequest(t, "PUT", urlStr) + session.MakeRequest(t, req, http.StatusCreated) + testSubscription(issue5, true) + + req = NewRequest(t, "PUT", urlStr) + session.MakeRequest(t, req, http.StatusOK) + testSubscription(issue5, true) +} diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go new file mode 100644 index 0000000000..3e651c620b --- /dev/null +++ b/tests/integration/api_issue_test.go @@ -0,0 +1,329 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "testing" + "time" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIListIssues(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repo.Name)) + + link.RawQuery = url.Values{"token": {token}, "state": {"all"}}.Encode() + resp := session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) + var apiIssues []*api.Issue + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, unittest.GetCount(t, &issues_model.Issue{RepoID: repo.ID})) + for _, apiIssue := range apiIssues { + unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: apiIssue.ID, RepoID: repo.ID}) + } + + // test milestone filter + link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "type": {"all"}, "milestones": {"ignore,milestone1,3,4"}}.Encode() + resp = session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + if assert.Len(t, apiIssues, 2) { + assert.EqualValues(t, 3, apiIssues[0].Milestone.ID) + assert.EqualValues(t, 1, apiIssues[1].Milestone.ID) + } + + link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "created_by": {"user2"}}.Encode() + resp = session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + if assert.Len(t, apiIssues, 1) { + assert.EqualValues(t, 5, apiIssues[0].ID) + } + + link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "assigned_by": {"user1"}}.Encode() + resp = session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + if assert.Len(t, apiIssues, 1) { + assert.EqualValues(t, 1, apiIssues[0].ID) + } + + link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "mentioned_by": {"user4"}}.Encode() + resp = session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + if assert.Len(t, apiIssues, 1) { + assert.EqualValues(t, 1, apiIssues[0].ID) + } +} + +func TestAPICreateIssue(t *testing.T) { + defer tests.PrepareTestEnv(t)() + const body, title = "apiTestBody", "apiTestTitle" + + repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID}) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues?state=all&token=%s", owner.Name, repoBefore.Name, token) + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{ + Body: body, + Title: title, + Assignee: owner.Name, + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + var apiIssue api.Issue + DecodeJSON(t, resp, &apiIssue) + assert.Equal(t, body, apiIssue.Body) + assert.Equal(t, title, apiIssue.Title) + + unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ + RepoID: repoBefore.ID, + AssigneeID: owner.ID, + Content: body, + Title: title, + }) + + repoAfter := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + assert.Equal(t, repoBefore.NumIssues+1, repoAfter.NumIssues) + assert.Equal(t, repoBefore.NumClosedIssues, repoAfter.NumClosedIssues) +} + +func TestAPIEditIssue(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) + repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID}) + assert.NoError(t, issueBefore.LoadAttributes(db.DefaultContext)) + assert.Equal(t, int64(1019307200), int64(issueBefore.DeadlineUnix)) + assert.Equal(t, api.StateOpen, issueBefore.State()) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + + // update values of issue + issueState := "closed" + removeDeadline := true + milestone := int64(4) + body := "new content!" + title := "new title from api set" + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d?token=%s", owner.Name, repoBefore.Name, issueBefore.Index, token) + req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{ + State: &issueState, + RemoveDeadline: &removeDeadline, + Milestone: &milestone, + Body: &body, + Title: title, + + // ToDo change more + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + var apiIssue api.Issue + DecodeJSON(t, resp, &apiIssue) + + issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) + repoAfter := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID}) + + // check deleted user + assert.Equal(t, int64(500), issueAfter.PosterID) + assert.NoError(t, issueAfter.LoadAttributes(db.DefaultContext)) + assert.Equal(t, int64(-1), issueAfter.PosterID) + assert.Equal(t, int64(-1), issueBefore.PosterID) + assert.Equal(t, int64(-1), apiIssue.Poster.ID) + + // check repo change + assert.Equal(t, repoBefore.NumClosedIssues+1, repoAfter.NumClosedIssues) + + // API response + assert.Equal(t, api.StateClosed, apiIssue.State) + assert.Equal(t, milestone, apiIssue.Milestone.ID) + assert.Equal(t, body, apiIssue.Body) + assert.True(t, apiIssue.Deadline == nil) + assert.Equal(t, title, apiIssue.Title) + + // in database + assert.Equal(t, api.StateClosed, issueAfter.State()) + assert.Equal(t, milestone, issueAfter.MilestoneID) + assert.Equal(t, int64(0), int64(issueAfter.DeadlineUnix)) + assert.Equal(t, body, issueAfter.Content) + assert.Equal(t, title, issueAfter.Title) +} + +func TestAPISearchIssues(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + token := getUserToken(t, "user2") + + // as this API was used in the frontend, it uses UI page size + expectedIssueCount := 15 // from the fixtures + if expectedIssueCount > setting.UI.IssuePagingNum { + expectedIssueCount = setting.UI.IssuePagingNum + } + + link, _ := url.Parse("/api/v1/repos/issues/search") + query := url.Values{"token": {getUserToken(t, "user1")}} + var apiIssues []*api.Issue + + link.RawQuery = query.Encode() + req := NewRequest(t, "GET", link.String()) + resp := MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, expectedIssueCount) + + since := "2000-01-01T00%3A50%3A01%2B00%3A00" // 946687801 + before := time.Unix(999307200, 0).Format(time.RFC3339) + query.Add("since", since) + query.Add("before", before) + query.Add("token", token) + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 8) + query.Del("since") + query.Del("before") + + query.Add("state", "closed") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + query.Set("state", "all") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.EqualValues(t, "17", resp.Header().Get("X-Total-Count")) + assert.Len(t, apiIssues, 17) + + query.Add("limit", "10") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.EqualValues(t, "17", resp.Header().Get("X-Total-Count")) + assert.Len(t, apiIssues, 10) + + query = url.Values{"assigned": {"true"}, "state": {"all"}, "token": {token}} + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + query = url.Values{"milestones": {"milestone1"}, "state": {"all"}, "token": {token}} + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 1) + + query = url.Values{"milestones": {"milestone1,milestone3"}, "state": {"all"}, "token": {token}} + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + query = url.Values{"owner": {"user2"}, "token": {token}} // user + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 6) + + query = url.Values{"owner": {"user3"}, "token": {token}} // organization + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 5) + + query = url.Values{"owner": {"user3"}, "team": {"team1"}, "token": {token}} // organization + team + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) +} + +func TestAPISearchIssuesWithLabels(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // as this API was used in the frontend, it uses UI page size + expectedIssueCount := 15 // from the fixtures + if expectedIssueCount > setting.UI.IssuePagingNum { + expectedIssueCount = setting.UI.IssuePagingNum + } + + link, _ := url.Parse("/api/v1/repos/issues/search") + query := url.Values{"token": {getUserToken(t, "user1")}} + var apiIssues []*api.Issue + + link.RawQuery = query.Encode() + req := NewRequest(t, "GET", link.String()) + resp := MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, expectedIssueCount) + + query.Add("labels", "label1") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + // multiple labels + query.Set("labels", "label1,label2") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + // an org label + query.Set("labels", "orglabel4") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 1) + + // org and repo label + query.Set("labels", "label2,orglabel4") + query.Add("state", "all") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + // org and repo label which share the same issue + query.Set("labels", "label1,orglabel4") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) +} diff --git a/tests/integration/api_issue_tracked_time_test.go b/tests/integration/api_issue_tracked_time_test.go new file mode 100644 index 0000000000..6e2c77030c --- /dev/null +++ b/tests/integration/api_issue_tracked_time_test.go @@ -0,0 +1,125 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + "time" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIGetTrackedTimes(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + assert.NoError(t, issue2.LoadRepo(db.DefaultContext)) + + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/times?token=%s", user2.Name, issue2.Repo.Name, issue2.Index, token) + resp := session.MakeRequest(t, req, http.StatusOK) + var apiTimes api.TrackedTimeList + DecodeJSON(t, resp, &apiTimes) + expect, err := issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{IssueID: issue2.ID}) + assert.NoError(t, err) + assert.Len(t, apiTimes, 3) + + for i, time := range expect { + assert.Equal(t, time.ID, apiTimes[i].ID) + assert.EqualValues(t, issue2.Title, apiTimes[i].Issue.Title) + assert.EqualValues(t, issue2.ID, apiTimes[i].IssueID) + assert.Equal(t, time.Created.Unix(), apiTimes[i].Created.Unix()) + assert.Equal(t, time.Time, apiTimes[i].Time) + user, err := user_model.GetUserByID(time.UserID) + assert.NoError(t, err) + assert.Equal(t, user.Name, apiTimes[i].UserName) + } + + // test filter + since := "2000-01-01T00%3A00%3A02%2B00%3A00" // 946684802 + before := "2000-01-01T00%3A00%3A12%2B00%3A00" // 946684812 + + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/times?since=%s&before=%s&token=%s", user2.Name, issue2.Repo.Name, issue2.Index, since, before, token) + resp = session.MakeRequest(t, req, http.StatusOK) + var filterAPITimes api.TrackedTimeList + DecodeJSON(t, resp, &filterAPITimes) + assert.Len(t, filterAPITimes, 2) + assert.Equal(t, int64(3), filterAPITimes[0].ID) + assert.Equal(t, int64(6), filterAPITimes[1].ID) +} + +func TestAPIDeleteTrackedTime(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + time6 := unittest.AssertExistsAndLoadBean(t, &issues_model.TrackedTime{ID: 6}) + issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + assert.NoError(t, issue2.LoadRepo(db.DefaultContext)) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session) + + // Deletion not allowed + req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/%d/times/%d?token=%s", user2.Name, issue2.Repo.Name, issue2.Index, time6.ID, token) + session.MakeRequest(t, req, http.StatusForbidden) + + time3 := unittest.AssertExistsAndLoadBean(t, &issues_model.TrackedTime{ID: 3}) + req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/%d/times/%d?token=%s", user2.Name, issue2.Repo.Name, issue2.Index, time3.ID, token) + session.MakeRequest(t, req, http.StatusNoContent) + // Delete non existing time + session.MakeRequest(t, req, http.StatusNotFound) + + // Reset time of user 2 on issue 2 + trackedSeconds, err := issues_model.GetTrackedSeconds(db.DefaultContext, issues_model.FindTrackedTimesOptions{IssueID: 2, UserID: 2}) + assert.NoError(t, err) + assert.Equal(t, int64(3661), trackedSeconds) + + req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/%d/times?token=%s", user2.Name, issue2.Repo.Name, issue2.Index, token) + session.MakeRequest(t, req, http.StatusNoContent) + session.MakeRequest(t, req, http.StatusNotFound) + + trackedSeconds, err = issues_model.GetTrackedSeconds(db.DefaultContext, issues_model.FindTrackedTimesOptions{IssueID: 2, UserID: 2}) + assert.NoError(t, err) + assert.Equal(t, int64(0), trackedSeconds) +} + +func TestAPIAddTrackedTimes(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + assert.NoError(t, issue2.LoadRepo(db.DefaultContext)) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + session := loginUser(t, admin.Name) + token := getTokenForLoggedInUser(t, session) + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/times?token=%s", user2.Name, issue2.Repo.Name, issue2.Index, token) + + req := NewRequestWithJSON(t, "POST", urlStr, &api.AddTimeOption{ + Time: 33, + User: user2.Name, + Created: time.Unix(947688818, 0), + }) + resp := session.MakeRequest(t, req, http.StatusOK) + var apiNewTime api.TrackedTime + DecodeJSON(t, resp, &apiNewTime) + + assert.EqualValues(t, 33, apiNewTime.Time) + assert.EqualValues(t, user2.ID, apiNewTime.UserID) + assert.EqualValues(t, 947688818, apiNewTime.Created.Unix()) +} diff --git a/tests/integration/api_keys_test.go b/tests/integration/api_keys_test.go new file mode 100644 index 0000000000..1cb0b20ffe --- /dev/null +++ b/tests/integration/api_keys_test.go @@ -0,0 +1,201 @@ +// Copyright 2017 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "testing" + + asymkey_model "code.gitea.io/gitea/models/asymkey" + "code.gitea.io/gitea/models/perm" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestViewDeployKeysNoLogin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/keys") + MakeRequest(t, req, http.StatusUnauthorized) +} + +func TestCreateDeployKeyNoLogin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/keys", api.CreateKeyOption{ + Title: "title", + Key: "key", + }) + MakeRequest(t, req, http.StatusUnauthorized) +} + +func TestGetDeployKeyNoLogin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/keys/1") + MakeRequest(t, req, http.StatusUnauthorized) +} + +func TestDeleteDeployKeyNoLogin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequest(t, "DELETE", "/api/v1/repos/user2/repo1/keys/1") + MakeRequest(t, req, http.StatusUnauthorized) +} + +func TestCreateReadOnlyDeployKey(t *testing.T) { + defer tests.PrepareTestEnv(t)() + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo1"}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, repoOwner.Name) + token := getTokenForLoggedInUser(t, session) + keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys?token=%s", repoOwner.Name, repo.Name, token) + rawKeyBody := api.CreateKeyOption{ + Title: "read-only", + Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", + ReadOnly: true, + } + req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody) + resp := session.MakeRequest(t, req, http.StatusCreated) + + var newDeployKey api.DeployKey + DecodeJSON(t, resp, &newDeployKey) + unittest.AssertExistsAndLoadBean(t, &asymkey_model.DeployKey{ + ID: newDeployKey.ID, + Name: rawKeyBody.Title, + Content: rawKeyBody.Key, + Mode: perm.AccessModeRead, + }) +} + +func TestCreateReadWriteDeployKey(t *testing.T) { + defer tests.PrepareTestEnv(t)() + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo1"}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, repoOwner.Name) + token := getTokenForLoggedInUser(t, session) + keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys?token=%s", repoOwner.Name, repo.Name, token) + rawKeyBody := api.CreateKeyOption{ + Title: "read-write", + Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", + } + req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody) + resp := session.MakeRequest(t, req, http.StatusCreated) + + var newDeployKey api.DeployKey + DecodeJSON(t, resp, &newDeployKey) + unittest.AssertExistsAndLoadBean(t, &asymkey_model.DeployKey{ + ID: newDeployKey.ID, + Name: rawKeyBody.Title, + Content: rawKeyBody.Key, + Mode: perm.AccessModeWrite, + }) +} + +func TestCreateUserKey(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"}) + + session := loginUser(t, "user1") + token := url.QueryEscape(getTokenForLoggedInUser(t, session)) + keysURL := fmt.Sprintf("/api/v1/user/keys?token=%s", token) + keyType := "ssh-rsa" + keyContent := "AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=" + rawKeyBody := api.CreateKeyOption{ + Title: "test-key", + Key: keyType + " " + keyContent, + } + req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody) + resp := session.MakeRequest(t, req, http.StatusCreated) + + var newPublicKey api.PublicKey + DecodeJSON(t, resp, &newPublicKey) + fingerprint, err := asymkey_model.CalcFingerprint(rawKeyBody.Key) + assert.NoError(t, err) + unittest.AssertExistsAndLoadBean(t, &asymkey_model.PublicKey{ + ID: newPublicKey.ID, + OwnerID: user.ID, + Name: rawKeyBody.Title, + Fingerprint: fingerprint, + Mode: perm.AccessModeWrite, + }) + + // Search by fingerprint + fingerprintURL := fmt.Sprintf("/api/v1/user/keys?token=%s&fingerprint=%s", token, newPublicKey.Fingerprint) + + req = NewRequest(t, "GET", fingerprintURL) + resp = session.MakeRequest(t, req, http.StatusOK) + + var fingerprintPublicKeys []api.PublicKey + DecodeJSON(t, resp, &fingerprintPublicKeys) + assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) + assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) + assert.Equal(t, user.ID, fingerprintPublicKeys[0].Owner.ID) + + fingerprintURL = fmt.Sprintf("/api/v1/users/%s/keys?token=%s&fingerprint=%s", user.Name, token, newPublicKey.Fingerprint) + + req = NewRequest(t, "GET", fingerprintURL) + resp = session.MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &fingerprintPublicKeys) + assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) + assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) + assert.Equal(t, user.ID, fingerprintPublicKeys[0].Owner.ID) + + // Fail search by fingerprint + fingerprintURL = fmt.Sprintf("/api/v1/user/keys?token=%s&fingerprint=%sA", token, newPublicKey.Fingerprint) + + req = NewRequest(t, "GET", fingerprintURL) + resp = session.MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &fingerprintPublicKeys) + assert.Len(t, fingerprintPublicKeys, 0) + + // Fail searching for wrong users key + fingerprintURL = fmt.Sprintf("/api/v1/users/%s/keys?token=%s&fingerprint=%s", "user2", token, newPublicKey.Fingerprint) + req = NewRequest(t, "GET", fingerprintURL) + resp = session.MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &fingerprintPublicKeys) + assert.Len(t, fingerprintPublicKeys, 0) + + // Now login as user 2 + session2 := loginUser(t, "user2") + token2 := url.QueryEscape(getTokenForLoggedInUser(t, session2)) + + // Should find key even though not ours, but we shouldn't know whose it is + fingerprintURL = fmt.Sprintf("/api/v1/user/keys?token=%s&fingerprint=%s", token2, newPublicKey.Fingerprint) + req = NewRequest(t, "GET", fingerprintURL) + resp = session.MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &fingerprintPublicKeys) + assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) + assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) + assert.Nil(t, fingerprintPublicKeys[0].Owner) + + // Should find key even though not ours, but we shouldn't know whose it is + fingerprintURL = fmt.Sprintf("/api/v1/users/%s/keys?token=%s&fingerprint=%s", user.Name, token2, newPublicKey.Fingerprint) + + req = NewRequest(t, "GET", fingerprintURL) + resp = session.MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &fingerprintPublicKeys) + assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) + assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) + assert.Nil(t, fingerprintPublicKeys[0].Owner) + + // Fail when searching for key if it is not ours + fingerprintURL = fmt.Sprintf("/api/v1/users/%s/keys?token=%s&fingerprint=%s", "user2", token2, newPublicKey.Fingerprint) + req = NewRequest(t, "GET", fingerprintURL) + resp = session.MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &fingerprintPublicKeys) + assert.Len(t, fingerprintPublicKeys, 0) +} diff --git a/tests/integration/api_nodeinfo_test.go b/tests/integration/api_nodeinfo_test.go new file mode 100644 index 0000000000..76f9105a51 --- /dev/null +++ b/tests/integration/api_nodeinfo_test.go @@ -0,0 +1,39 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "context" + "net/http" + "net/url" + "testing" + + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/routers" + + "github.com/stretchr/testify/assert" +) + +func TestNodeinfo(t *testing.T) { + setting.Federation.Enabled = true + c = routers.NormalRoutes(context.TODO()) + defer func() { + setting.Federation.Enabled = false + c = routers.NormalRoutes(context.TODO()) + }() + + onGiteaRun(t, func(*testing.T, *url.URL) { + req := NewRequestf(t, "GET", "/api/v1/nodeinfo") + resp := MakeRequest(t, req, http.StatusOK) + var nodeinfo api.NodeInfo + DecodeJSON(t, resp, &nodeinfo) + assert.True(t, nodeinfo.OpenRegistrations) + assert.Equal(t, "gitea", nodeinfo.Software.Name) + assert.Equal(t, 23, nodeinfo.Usage.Users.Total) + assert.Equal(t, 17, nodeinfo.Usage.LocalPosts) + assert.Equal(t, 2, nodeinfo.Usage.LocalComments) + }) +} diff --git a/tests/integration/api_notification_test.go b/tests/integration/api_notification_test.go new file mode 100644 index 0000000000..bf85520bb5 --- /dev/null +++ b/tests/integration/api_notification_test.go @@ -0,0 +1,193 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + activities_model "code.gitea.io/gitea/models/activities" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPINotification(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}) + thread5 := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5}) + assert.NoError(t, thread5.LoadAttributes()) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session) + + // -- GET /notifications -- + // test filter + since := "2000-01-01T00%3A50%3A01%2B00%3A00" // 946687801 + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?since=%s&token=%s", since, token)) + resp := session.MakeRequest(t, req, http.StatusOK) + var apiNL []api.NotificationThread + DecodeJSON(t, resp, &apiNL) + + assert.Len(t, apiNL, 1) + assert.EqualValues(t, 5, apiNL[0].ID) + + // test filter + before := "2000-01-01T01%3A06%3A59%2B00%3A00" // 946688819 + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?all=%s&before=%s&token=%s", "true", before, token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiNL) + + assert.Len(t, apiNL, 3) + assert.EqualValues(t, 4, apiNL[0].ID) + assert.True(t, apiNL[0].Unread) + assert.False(t, apiNL[0].Pinned) + assert.EqualValues(t, 3, apiNL[1].ID) + assert.False(t, apiNL[1].Unread) + assert.True(t, apiNL[1].Pinned) + assert.EqualValues(t, 2, apiNL[2].ID) + assert.False(t, apiNL[2].Unread) + assert.False(t, apiNL[2].Pinned) + + // -- GET /repos/{owner}/{repo}/notifications -- + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?status-types=unread&token=%s", user2.Name, repo1.Name, token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiNL) + + assert.Len(t, apiNL, 1) + assert.EqualValues(t, 4, apiNL[0].ID) + + // -- GET /repos/{owner}/{repo}/notifications -- multiple status-types + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?status-types=unread&status-types=pinned&token=%s", user2.Name, repo1.Name, token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiNL) + + assert.Len(t, apiNL, 2) + assert.EqualValues(t, 4, apiNL[0].ID) + assert.True(t, apiNL[0].Unread) + assert.False(t, apiNL[0].Pinned) + assert.EqualValues(t, 3, apiNL[1].ID) + assert.False(t, apiNL[1].Unread) + assert.True(t, apiNL[1].Pinned) + + // -- GET /notifications/threads/{id} -- + // get forbidden + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/threads/%d?token=%s", 1, token)) + session.MakeRequest(t, req, http.StatusForbidden) + + // get own + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/threads/%d?token=%s", thread5.ID, token)) + resp = session.MakeRequest(t, req, http.StatusOK) + var apiN api.NotificationThread + DecodeJSON(t, resp, &apiN) + + assert.EqualValues(t, 5, apiN.ID) + assert.False(t, apiN.Pinned) + assert.True(t, apiN.Unread) + assert.EqualValues(t, "issue4", apiN.Subject.Title) + assert.EqualValues(t, "Issue", apiN.Subject.Type) + assert.EqualValues(t, thread5.Issue.APIURL(), apiN.Subject.URL) + assert.EqualValues(t, thread5.Repository.HTMLURL(), apiN.Repository.HTMLURL) + + new := struct { + New int64 `json:"new"` + }{} + + // -- check notifications -- + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/new?token=%s", token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &new) + assert.True(t, new.New > 0) + + // -- mark notifications as read -- + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiNL) + assert.Len(t, apiNL, 2) + + lastReadAt := "2000-01-01T00%3A50%3A01%2B00%3A00" // 946687801 <- only Notification 4 is in this filter ... + req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token)) + session.MakeRequest(t, req, http.StatusResetContent) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiNL) + assert.Len(t, apiNL, 1) + + // -- PATCH /notifications/threads/{id} -- + req = NewRequest(t, "PATCH", fmt.Sprintf("/api/v1/notifications/threads/%d?token=%s", thread5.ID, token)) + session.MakeRequest(t, req, http.StatusResetContent) + + assert.Equal(t, activities_model.NotificationStatusUnread, thread5.Status) + thread5 = unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5}) + assert.Equal(t, activities_model.NotificationStatusRead, thread5.Status) + + // -- check notifications -- + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/new?token=%s", token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &new) + assert.True(t, new.New == 0) +} + +func TestAPINotificationPUT(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + thread5 := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5}) + assert.NoError(t, thread5.LoadAttributes()) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session) + + // Check notifications are as expected + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?all=true&token=%s", token)) + resp := session.MakeRequest(t, req, http.StatusOK) + var apiNL []api.NotificationThread + DecodeJSON(t, resp, &apiNL) + + assert.Len(t, apiNL, 4) + assert.EqualValues(t, 5, apiNL[0].ID) + assert.True(t, apiNL[0].Unread) + assert.False(t, apiNL[0].Pinned) + assert.EqualValues(t, 4, apiNL[1].ID) + assert.True(t, apiNL[1].Unread) + assert.False(t, apiNL[1].Pinned) + assert.EqualValues(t, 3, apiNL[2].ID) + assert.False(t, apiNL[2].Unread) + assert.True(t, apiNL[2].Pinned) + assert.EqualValues(t, 2, apiNL[3].ID) + assert.False(t, apiNL[3].Unread) + assert.False(t, apiNL[3].Pinned) + + // + // Notification ID 2 is the only one with status-type read & pinned + // change it to unread. + // + req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/notifications?status-types=read&status-type=pinned&to-status=unread&token=%s", token)) + resp = session.MakeRequest(t, req, http.StatusResetContent) + DecodeJSON(t, resp, &apiNL) + assert.Len(t, apiNL, 1) + assert.EqualValues(t, 2, apiNL[0].ID) + assert.True(t, apiNL[0].Unread) + assert.False(t, apiNL[0].Pinned) + + // + // Now nofication ID 2 is the first in the list and is unread. + // + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?all=true&token=%s", token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiNL) + + assert.Len(t, apiNL, 4) + assert.EqualValues(t, 2, apiNL[0].ID) + assert.True(t, apiNL[0].Unread) + assert.False(t, apiNL[0].Pinned) +} diff --git a/tests/integration/api_oauth2_apps_test.go b/tests/integration/api_oauth2_apps_test.go new file mode 100644 index 0000000000..fe3525724e --- /dev/null +++ b/tests/integration/api_oauth2_apps_test.go @@ -0,0 +1,166 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file.package models + +package integration + +import ( + "fmt" + "net/http" + "testing" + + "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestOAuth2Application(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testAPICreateOAuth2Application(t) + testAPIListOAuth2Applications(t) + testAPIGetOAuth2Application(t) + testAPIUpdateOAuth2Application(t) + testAPIDeleteOAuth2Application(t) +} + +func testAPICreateOAuth2Application(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + appBody := api.CreateOAuth2ApplicationOptions{ + Name: "test-app-1", + RedirectURIs: []string{ + "http://www.google.com", + }, + } + + req := NewRequestWithJSON(t, "POST", "/api/v1/user/applications/oauth2", &appBody) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusCreated) + + var createdApp *api.OAuth2Application + DecodeJSON(t, resp, &createdApp) + + assert.EqualValues(t, appBody.Name, createdApp.Name) + assert.Len(t, createdApp.ClientSecret, 56) + assert.Len(t, createdApp.ClientID, 36) + assert.NotEmpty(t, createdApp.Created) + assert.EqualValues(t, appBody.RedirectURIs[0], createdApp.RedirectURIs[0]) + unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{UID: user.ID, Name: createdApp.Name}) +} + +func testAPIListOAuth2Applications(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + existApp := unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ + UID: user.ID, + Name: "test-app-1", + RedirectURIs: []string{ + "http://www.google.com", + }, + }) + + urlStr := fmt.Sprintf("/api/v1/user/applications/oauth2?token=%s", token) + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + + var appList api.OAuth2ApplicationList + DecodeJSON(t, resp, &appList) + expectedApp := appList[0] + + assert.EqualValues(t, existApp.Name, expectedApp.Name) + assert.EqualValues(t, existApp.ClientID, expectedApp.ClientID) + assert.Len(t, expectedApp.ClientID, 36) + assert.Empty(t, expectedApp.ClientSecret) + assert.EqualValues(t, existApp.RedirectURIs[0], expectedApp.RedirectURIs[0]) + unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) +} + +func testAPIDeleteOAuth2Application(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + oldApp := unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ + UID: user.ID, + Name: "test-app-1", + }) + + urlStr := fmt.Sprintf("/api/v1/user/applications/oauth2/%d?token=%s", oldApp.ID, token) + req := NewRequest(t, "DELETE", urlStr) + session.MakeRequest(t, req, http.StatusNoContent) + + unittest.AssertNotExistsBean(t, &auth.OAuth2Application{UID: oldApp.UID, Name: oldApp.Name}) + + // Delete again will return not found + req = NewRequest(t, "DELETE", urlStr) + session.MakeRequest(t, req, http.StatusNotFound) +} + +func testAPIGetOAuth2Application(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + existApp := unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ + UID: user.ID, + Name: "test-app-1", + RedirectURIs: []string{ + "http://www.google.com", + }, + }) + + urlStr := fmt.Sprintf("/api/v1/user/applications/oauth2/%d?token=%s", existApp.ID, token) + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + + var app api.OAuth2Application + DecodeJSON(t, resp, &app) + expectedApp := app + + assert.EqualValues(t, existApp.Name, expectedApp.Name) + assert.EqualValues(t, existApp.ClientID, expectedApp.ClientID) + assert.Len(t, expectedApp.ClientID, 36) + assert.Empty(t, expectedApp.ClientSecret) + assert.Len(t, expectedApp.RedirectURIs, 1) + assert.EqualValues(t, existApp.RedirectURIs[0], expectedApp.RedirectURIs[0]) + unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) +} + +func testAPIUpdateOAuth2Application(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + existApp := unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ + UID: user.ID, + Name: "test-app-1", + RedirectURIs: []string{ + "http://www.google.com", + }, + }) + + appBody := api.CreateOAuth2ApplicationOptions{ + Name: "test-app-1", + RedirectURIs: []string{ + "http://www.google.com/", + "http://www.github.com/", + }, + } + + urlStr := fmt.Sprintf("/api/v1/user/applications/oauth2/%d", existApp.ID) + req := NewRequestWithJSON(t, "PATCH", urlStr, &appBody) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var app api.OAuth2Application + DecodeJSON(t, resp, &app) + expectedApp := app + + assert.Len(t, expectedApp.RedirectURIs, 2) + assert.EqualValues(t, expectedApp.RedirectURIs[0], appBody.RedirectURIs[0]) + assert.EqualValues(t, expectedApp.RedirectURIs[1], appBody.RedirectURIs[1]) + unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) +} diff --git a/tests/integration/api_org_test.go b/tests/integration/api_org_test.go new file mode 100644 index 0000000000..70bb17bee2 --- /dev/null +++ b/tests/integration/api_org_test.go @@ -0,0 +1,153 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "strings" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIOrgCreate(t *testing.T) { + onGiteaRun(t, func(*testing.T, *url.URL) { + token := getUserToken(t, "user1") + + org := api.CreateOrgOption{ + UserName: "user1_org", + FullName: "User1's organization", + Description: "This organization created by user1", + Website: "https://try.gitea.io", + Location: "Shanghai", + Visibility: "limited", + } + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs?token="+token, &org) + resp := MakeRequest(t, req, http.StatusCreated) + + var apiOrg api.Organization + DecodeJSON(t, resp, &apiOrg) + + assert.Equal(t, org.UserName, apiOrg.UserName) + assert.Equal(t, org.FullName, apiOrg.FullName) + assert.Equal(t, org.Description, apiOrg.Description) + assert.Equal(t, org.Website, apiOrg.Website) + assert.Equal(t, org.Location, apiOrg.Location) + assert.Equal(t, org.Visibility, apiOrg.Visibility) + + unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: org.UserName, + LowerName: strings.ToLower(org.UserName), + FullName: org.FullName, + }) + + req = NewRequestf(t, "GET", "/api/v1/orgs/%s?token=%s", org.UserName, token) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiOrg) + assert.EqualValues(t, org.UserName, apiOrg.UserName) + + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos?token=%s", org.UserName, token) + resp = MakeRequest(t, req, http.StatusOK) + + var repos []*api.Repository + DecodeJSON(t, resp, &repos) + for _, repo := range repos { + assert.False(t, repo.Private) + } + + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members?token=%s", org.UserName, token) + resp = MakeRequest(t, req, http.StatusOK) + + // user1 on this org is public + var users []*api.User + DecodeJSON(t, resp, &users) + assert.Len(t, users, 1) + assert.EqualValues(t, "user1", users[0].UserName) + }) +} + +func TestAPIOrgEdit(t *testing.T) { + onGiteaRun(t, func(*testing.T, *url.URL) { + session := loginUser(t, "user1") + + token := getTokenForLoggedInUser(t, session) + org := api.EditOrgOption{ + FullName: "User3 organization new full name", + Description: "A new description", + Website: "https://try.gitea.io/new", + Location: "Beijing", + Visibility: "private", + } + req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/user3?token="+token, &org) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiOrg api.Organization + DecodeJSON(t, resp, &apiOrg) + + assert.Equal(t, "user3", apiOrg.UserName) + assert.Equal(t, org.FullName, apiOrg.FullName) + assert.Equal(t, org.Description, apiOrg.Description) + assert.Equal(t, org.Website, apiOrg.Website) + assert.Equal(t, org.Location, apiOrg.Location) + assert.Equal(t, org.Visibility, apiOrg.Visibility) + }) +} + +func TestAPIOrgEditBadVisibility(t *testing.T) { + onGiteaRun(t, func(*testing.T, *url.URL) { + session := loginUser(t, "user1") + + token := getTokenForLoggedInUser(t, session) + org := api.EditOrgOption{ + FullName: "User3 organization new full name", + Description: "A new description", + Website: "https://try.gitea.io/new", + Location: "Beijing", + Visibility: "badvisibility", + } + req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/user3?token="+token, &org) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + }) +} + +func TestAPIOrgDeny(t *testing.T) { + onGiteaRun(t, func(*testing.T, *url.URL) { + setting.Service.RequireSignInView = true + defer func() { + setting.Service.RequireSignInView = false + }() + + orgName := "user1_org" + req := NewRequestf(t, "GET", "/api/v1/orgs/%s", orgName) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", orgName) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members", orgName) + MakeRequest(t, req, http.StatusNotFound) + }) +} + +func TestAPIGetAll(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequestf(t, "GET", "/api/v1/orgs") + resp := MakeRequest(t, req, http.StatusOK) + + var apiOrgList []*api.Organization + DecodeJSON(t, resp, &apiOrgList) + + assert.Len(t, apiOrgList, 7) + assert.Equal(t, "org25", apiOrgList[0].FullName) + assert.Equal(t, "public", apiOrgList[0].Visibility) +} diff --git a/tests/integration/api_packages_composer_test.go b/tests/integration/api_packages_composer_test.go new file mode 100644 index 0000000000..90285f78d3 --- /dev/null +++ b/tests/integration/api_packages_composer_test.go @@ -0,0 +1,215 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "archive/zip" + "bytes" + "fmt" + "net/http" + neturl "net/url" + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + composer_module "code.gitea.io/gitea/modules/packages/composer" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/routers/api/packages/composer" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestPackageComposer(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + vendorName := "gitea" + projectName := "composer-package" + packageName := vendorName + "/" + projectName + packageVersion := "1.0.3" + packageDescription := "Package Description" + packageType := "composer-plugin" + packageAuthor := "Gitea Authors" + packageLicense := "MIT" + + var buf bytes.Buffer + archive := zip.NewWriter(&buf) + w, _ := archive.Create("composer.json") + w.Write([]byte(`{ + "name": "` + packageName + `", + "description": "` + packageDescription + `", + "type": "` + packageType + `", + "license": "` + packageLicense + `", + "authors": [ + { + "name": "` + packageAuthor + `" + } + ] + }`)) + archive.Close() + content := buf.Bytes() + + url := fmt.Sprintf("%sapi/packages/%s/composer", setting.AppURL, user.Name) + + t.Run("ServiceIndex", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/packages.json", url)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var result composer.ServiceIndexResponse + DecodeJSON(t, resp, &result) + + assert.Equal(t, url+"/search.json?q=%query%&type=%type%", result.SearchTemplate) + assert.Equal(t, url+"/p2/%package%.json", result.MetadataTemplate) + assert.Equal(t, url+"/list.json", result.PackageList) + }) + + t.Run("Upload", func(t *testing.T) { + t.Run("MissingVersion", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusBadRequest) + }) + + t.Run("Valid", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + uploadURL := url + "?version=" + packageVersion + + req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(content)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusCreated) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeComposer) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.IsType(t, &composer_module.Metadata{}, pd.Metadata) + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + assert.Equal(t, fmt.Sprintf("%s-%s.%s.zip", vendorName, projectName, packageVersion), pfs[0].Name) + assert.True(t, pfs[0].IsLead) + + pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(len(content)), pb.Size) + + req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(content)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusBadRequest) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeComposer) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + assert.Equal(t, int64(0), pvs[0].DownloadCount) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + + req := NewRequest(t, "GET", fmt.Sprintf("%s/files/%s/%s/%s", url, neturl.PathEscape(packageName), neturl.PathEscape(pvs[0].LowerVersion), neturl.PathEscape(pfs[0].LowerName))) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, content, resp.Body.Bytes()) + + pvs, err = packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeComposer) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + assert.Equal(t, int64(1), pvs[0].DownloadCount) + }) + + t.Run("SearchService", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + cases := []struct { + Query string + Type string + Page int + PerPage int + ExpectedTotal int64 + ExpectedResults int + }{ + {"", "", 0, 0, 1, 1}, + {"", "", 1, 1, 1, 1}, + {"test", "", 1, 0, 0, 0}, + {"gitea", "", 1, 1, 1, 1}, + {"gitea", "", 2, 1, 1, 0}, + {"", packageType, 1, 1, 1, 1}, + {"gitea", packageType, 1, 1, 1, 1}, + {"gitea", "dummy", 1, 1, 0, 0}, + } + + for i, c := range cases { + req := NewRequest(t, "GET", fmt.Sprintf("%s/search.json?q=%s&type=%s&page=%d&per_page=%d", url, c.Query, c.Type, c.Page, c.PerPage)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var result composer.SearchResultResponse + DecodeJSON(t, resp, &result) + + assert.Equal(t, c.ExpectedTotal, result.Total, "case %d: unexpected total hits", i) + assert.Len(t, result.Results, c.ExpectedResults, "case %d: unexpected result count", i) + } + }) + + t.Run("EnumeratePackages", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", url+"/list.json") + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var result map[string][]string + DecodeJSON(t, resp, &result) + + assert.Contains(t, result, "packageNames") + names := result["packageNames"] + assert.Len(t, names, 1) + assert.Equal(t, packageName, names[0]) + }) + + t.Run("PackageMetadata", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var result composer.PackageMetadataResponse + DecodeJSON(t, resp, &result) + + assert.Contains(t, result.Packages, packageName) + pkgs := result.Packages[packageName] + assert.Len(t, pkgs, 1) + assert.Equal(t, packageName, pkgs[0].Name) + assert.Equal(t, packageVersion, pkgs[0].Version) + assert.Equal(t, packageType, pkgs[0].Type) + assert.Equal(t, packageDescription, pkgs[0].Description) + assert.Len(t, pkgs[0].Authors, 1) + assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name) + assert.Equal(t, "zip", pkgs[0].Dist.Type) + assert.Equal(t, "7b40bfd6da811b2b78deec1e944f156dbb2c747b", pkgs[0].Dist.Checksum) + }) +} diff --git a/tests/integration/api_packages_conan_test.go b/tests/integration/api_packages_conan_test.go new file mode 100644 index 0000000000..5b34417343 --- /dev/null +++ b/tests/integration/api_packages_conan_test.go @@ -0,0 +1,725 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + stdurl "net/url" + "strings" + "testing" + "time" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/packages" + conan_model "code.gitea.io/gitea/models/packages/conan" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + conan_module "code.gitea.io/gitea/modules/packages/conan" + "code.gitea.io/gitea/modules/setting" + conan_router "code.gitea.io/gitea/routers/api/packages/conan" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +const ( + conanfileName = "conanfile.py" + conaninfoName = "conaninfo.txt" + + conanLicense = "MIT" + conanAuthor = "Gitea <info@gitea.io>" + conanHomepage = "https://gitea.io/" + conanURL = "https://gitea.com/" + conanDescription = "Description of ConanPackage" + conanTopic = "gitea" + + conanPackageReference = "dummyreference" + + contentConaninfo = `[settings] + arch=x84_64 + +[requires] + fmt/7.1.3 + +[options] + shared=False + +[full_settings] + arch=x84_64 + +[full_requires] + fmt/7.1.3 + +[full_options] + shared=False + +[recipe_hash] + 74714915a51073acb548ca1ce29afbac + +[env] +CC=gcc-10` +) + +func addTokenAuthHeader(request *http.Request, token string) *http.Request { + request.Header.Set("Authorization", token) + return request +} + +func buildConanfileContent(name, version string) string { + return `from conans import ConanFile, CMake, tools + +class ConanPackageConan(ConanFile): + name = "` + name + `" + version = "` + version + `" + license = "` + conanLicense + `" + author = "` + conanAuthor + `" + homepage = "` + conanHomepage + `" + url = "` + conanURL + `" + description = "` + conanDescription + `" + topics = ("` + conanTopic + `") + settings = "os", "compiler", "build_type", "arch" + options = {"shared": [True, False], "fPIC": [True, False]} + default_options = {"shared": False, "fPIC": True} + generators = "cmake"` +} + +func uploadConanPackageV1(t *testing.T, baseURL, token, name, version, user, channel string) { + contentConanfile := buildConanfileContent(name, version) + + recipeURL := fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", baseURL, name, version, user, channel) + + req := NewRequest(t, "GET", recipeURL) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", recipeURL)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", recipeURL)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "POST", fmt.Sprintf("%s/upload_urls", recipeURL)) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/upload_urls", recipeURL), map[string]int64{ + conanfileName: int64(len(contentConanfile)), + "removed.txt": 0, + }) + req = addTokenAuthHeader(req, token) + resp := MakeRequest(t, req, http.StatusOK) + + uploadURLs := make(map[string]string) + DecodeJSON(t, resp, &uploadURLs) + + assert.Contains(t, uploadURLs, conanfileName) + assert.NotContains(t, uploadURLs, "removed.txt") + + uploadURL := uploadURLs[conanfileName] + assert.NotEmpty(t, uploadURL) + + req = NewRequestWithBody(t, "PUT", uploadURL, strings.NewReader(contentConanfile)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusCreated) + + packageURL := fmt.Sprintf("%s/packages/%s", recipeURL, conanPackageReference) + + req = NewRequest(t, "GET", packageURL) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", packageURL)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", packageURL)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "POST", fmt.Sprintf("%s/upload_urls", packageURL)) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/upload_urls", packageURL), map[string]int64{ + conaninfoName: int64(len(contentConaninfo)), + "removed.txt": 0, + }) + req = addTokenAuthHeader(req, token) + resp = MakeRequest(t, req, http.StatusOK) + + uploadURLs = make(map[string]string) + DecodeJSON(t, resp, &uploadURLs) + + assert.Contains(t, uploadURLs, conaninfoName) + assert.NotContains(t, uploadURLs, "removed.txt") + + uploadURL = uploadURLs[conaninfoName] + assert.NotEmpty(t, uploadURL) + + req = NewRequestWithBody(t, "PUT", uploadURL, strings.NewReader(contentConaninfo)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusCreated) +} + +func uploadConanPackageV2(t *testing.T, baseURL, token, name, version, user, channel, recipeRevision, packageRevision string) { + contentConanfile := buildConanfileContent(name, version) + + recipeURL := fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", baseURL, name, version, user, channel, recipeRevision) + + req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/files/%s", recipeURL, conanfileName), strings.NewReader(contentConanfile)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/files", recipeURL)) + req = addTokenAuthHeader(req, token) + resp := MakeRequest(t, req, http.StatusOK) + + var list *struct { + Files map[string]interface{} `json:"files"` + } + DecodeJSON(t, resp, &list) + assert.Len(t, list.Files, 1) + assert.Contains(t, list.Files, conanfileName) + + packageURL := fmt.Sprintf("%s/packages/%s/revisions/%s", recipeURL, conanPackageReference, packageRevision) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/files", packageURL)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/files/%s", packageURL, conaninfoName), strings.NewReader(contentConaninfo)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/files", packageURL)) + req = addTokenAuthHeader(req, token) + resp = MakeRequest(t, req, http.StatusOK) + + list = nil + DecodeJSON(t, resp, &list) + assert.Len(t, list.Files, 1) + assert.Contains(t, list.Files, conaninfoName) +} + +func TestPackageConan(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + name := "ConanPackage" + version1 := "1.2" + version2 := "1.3" + user1 := "dummy" + user2 := "gitea" + channel1 := "test" + channel2 := "final" + revision1 := "rev1" + revision2 := "rev2" + + url := fmt.Sprintf("%sapi/packages/%s/conan", setting.AppURL, user.Name) + + t.Run("v1", func(t *testing.T) { + t.Run("Ping", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/ping", url)) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, "revisions", resp.Header().Get("X-Conan-Server-Capabilities")) + }) + + token := "" + + t.Run("Authenticate", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/users/authenticate", url)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + body := resp.Body.String() + assert.NotEmpty(t, body) + + token = fmt.Sprintf("Bearer %s", body) + }) + + t.Run("CheckCredentials", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/users/check_credentials", url)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + uploadConanPackageV1(t, url, token, name, version1, user1, channel1) + + t.Run("Validate", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeConan) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.Equal(t, name, pd.Package.Name) + assert.Equal(t, version1, pd.Version.Version) + assert.IsType(t, &conan_module.Metadata{}, pd.Metadata) + metadata := pd.Metadata.(*conan_module.Metadata) + assert.Equal(t, conanLicense, metadata.License) + assert.Equal(t, conanAuthor, metadata.Author) + assert.Equal(t, conanHomepage, metadata.ProjectURL) + assert.Equal(t, conanURL, metadata.RepositoryURL) + assert.Equal(t, conanDescription, metadata.Description) + assert.Equal(t, []string{conanTopic}, metadata.Keywords) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 2) + + for _, pf := range pfs { + pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) + assert.NoError(t, err) + + if pf.Name == conanfileName { + assert.True(t, pf.IsLead) + + assert.Equal(t, int64(len(buildConanfileContent(name, version1))), pb.Size) + } else if pf.Name == conaninfoName { + assert.False(t, pf.IsLead) + + assert.Equal(t, int64(len(contentConaninfo)), pb.Size) + } else { + assert.Fail(t, "unknown file: %s", pf.Name) + } + } + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + recipeURL := fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", url, name, version1, user1, channel1) + + req := NewRequest(t, "GET", recipeURL) + resp := MakeRequest(t, req, http.StatusOK) + + fileHashes := make(map[string]string) + DecodeJSON(t, resp, &fileHashes) + assert.Len(t, fileHashes, 1) + assert.Contains(t, fileHashes, conanfileName) + assert.Equal(t, "7abc52241c22090782c54731371847a8", fileHashes[conanfileName]) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", recipeURL)) + resp = MakeRequest(t, req, http.StatusOK) + + downloadURLs := make(map[string]string) + DecodeJSON(t, resp, &downloadURLs) + assert.Contains(t, downloadURLs, conanfileName) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", recipeURL)) + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &downloadURLs) + assert.Contains(t, downloadURLs, conanfileName) + + req = NewRequest(t, "GET", downloadURLs[conanfileName]) + resp = MakeRequest(t, req, http.StatusOK) + assert.Equal(t, buildConanfileContent(name, version1), resp.Body.String()) + + packageURL := fmt.Sprintf("%s/packages/%s", recipeURL, conanPackageReference) + + req = NewRequest(t, "GET", packageURL) + resp = MakeRequest(t, req, http.StatusOK) + + fileHashes = make(map[string]string) + DecodeJSON(t, resp, &fileHashes) + assert.Len(t, fileHashes, 1) + assert.Contains(t, fileHashes, conaninfoName) + assert.Equal(t, "7628bfcc5b17f1470c468621a78df394", fileHashes[conaninfoName]) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", packageURL)) + resp = MakeRequest(t, req, http.StatusOK) + + downloadURLs = make(map[string]string) + DecodeJSON(t, resp, &downloadURLs) + assert.Contains(t, downloadURLs, conaninfoName) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", packageURL)) + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &downloadURLs) + assert.Contains(t, downloadURLs, conaninfoName) + + req = NewRequest(t, "GET", downloadURLs[conaninfoName]) + resp = MakeRequest(t, req, http.StatusOK) + assert.Equal(t, contentConaninfo, resp.Body.String()) + }) + + t.Run("Search", func(t *testing.T) { + uploadConanPackageV1(t, url, token, name, version2, user1, channel1) + uploadConanPackageV1(t, url, token, name, version1, user1, channel2) + uploadConanPackageV1(t, url, token, name, version1, user2, channel1) + uploadConanPackageV1(t, url, token, name, version1, user2, channel2) + + t.Run("Recipe", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + cases := []struct { + Query string + Expected []string + }{ + {"ConanPackage", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/1.2", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/1.1", []string{}}, + {"Conan*", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/*", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/1*", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/*2", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/1*2", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/1.2@", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/1.2@du*", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@dummy/final"}}, + {"ConanPackage/1.2@du*/", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@dummy/final"}}, + {"ConanPackage/1.2@du*/*test", []string{"ConanPackage/1.2@dummy/test"}}, + {"ConanPackage/1.2@du*/*st", []string{"ConanPackage/1.2@dummy/test"}}, + {"ConanPackage/1.2@gitea/*", []string{"ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"*/*@dummy", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@dummy/final"}}, + {"*/*@*/final", []string{"ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/final"}}, + } + + for i, c := range cases { + req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/conans/search?q=%s", url, stdurl.QueryEscape(c.Query))) + resp := MakeRequest(t, req, http.StatusOK) + + var result *conan_router.SearchResult + DecodeJSON(t, resp, &result) + + assert.ElementsMatch(t, c.Expected, result.Results, "case %d: unexpected result", i) + } + }) + + t.Run("Package", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s/search", url, name, version1, user1, channel2)) + resp := MakeRequest(t, req, http.StatusOK) + + var result map[string]*conan_module.Conaninfo + DecodeJSON(t, resp, &result) + + assert.Contains(t, result, conanPackageReference) + info := result[conanPackageReference] + assert.NotEmpty(t, info.Settings) + }) + }) + + t.Run("Delete", func(t *testing.T) { + t.Run("Package", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + cases := []struct { + Channel string + References []string + }{ + {channel1, []string{conanPackageReference}}, + {channel2, []string{}}, + } + + for i, c := range cases { + rref, _ := conan_module.NewRecipeReference(name, version1, user1, c.Channel, conan_module.DefaultRevision) + references, err := conan_model.GetPackageReferences(db.DefaultContext, user.ID, rref) + assert.NoError(t, err) + assert.NotEmpty(t, references) + + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s/packages/delete", url, name, version1, user1, c.Channel), map[string][]string{ + "package_ids": c.References, + }) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + + references, err = conan_model.GetPackageReferences(db.DefaultContext, user.ID, rref) + assert.NoError(t, err) + assert.Empty(t, references, "case %d: should be empty", i) + } + }) + + t.Run("Recipe", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + cases := []struct { + Channel string + }{ + {channel1}, + {channel2}, + } + + for i, c := range cases { + rref, _ := conan_module.NewRecipeReference(name, version1, user1, c.Channel, conan_module.DefaultRevision) + revisions, err := conan_model.GetRecipeRevisions(db.DefaultContext, user.ID, rref) + assert.NoError(t, err) + assert.NotEmpty(t, revisions) + + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", url, name, version1, user1, c.Channel)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + + revisions, err = conan_model.GetRecipeRevisions(db.DefaultContext, user.ID, rref) + assert.NoError(t, err) + assert.Empty(t, revisions, "case %d: should be empty", i) + } + }) + }) + }) + + t.Run("v2", func(t *testing.T) { + t.Run("Ping", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/ping", url)) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, "revisions", resp.Header().Get("X-Conan-Server-Capabilities")) + }) + + token := "" + + t.Run("Authenticate", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/users/authenticate", url)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + body := resp.Body.String() + assert.NotEmpty(t, body) + + token = fmt.Sprintf("Bearer %s", body) + }) + + t.Run("CheckCredentials", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/users/check_credentials", url)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + uploadConanPackageV2(t, url, token, name, version1, user1, channel1, revision1, revision1) + + t.Run("Validate", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeConan) + assert.NoError(t, err) + assert.Len(t, pvs, 2) + }) + }) + + t.Run("Latest", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + recipeURL := fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1) + + req := NewRequest(t, "GET", fmt.Sprintf("%s/latest", recipeURL)) + resp := MakeRequest(t, req, http.StatusOK) + + obj := make(map[string]string) + DecodeJSON(t, resp, &obj) + assert.Contains(t, obj, "revision") + assert.Equal(t, revision1, obj["revision"]) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/revisions/%s/packages/%s/latest", recipeURL, revision1, conanPackageReference)) + resp = MakeRequest(t, req, http.StatusOK) + + obj = make(map[string]string) + DecodeJSON(t, resp, &obj) + assert.Contains(t, obj, "revision") + assert.Equal(t, revision1, obj["revision"]) + }) + + t.Run("ListRevisions", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + uploadConanPackageV2(t, url, token, name, version1, user1, channel1, revision1, revision2) + uploadConanPackageV2(t, url, token, name, version1, user1, channel1, revision2, revision1) + uploadConanPackageV2(t, url, token, name, version1, user1, channel1, revision2, revision2) + + recipeURL := fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions", url, name, version1, user1, channel1) + + req := NewRequest(t, "GET", recipeURL) + resp := MakeRequest(t, req, http.StatusOK) + + type RevisionInfo struct { + Revision string `json:"revision"` + Time time.Time `json:"time"` + } + + type RevisionList struct { + Revisions []*RevisionInfo `json:"revisions"` + } + + var list *RevisionList + DecodeJSON(t, resp, &list) + assert.Len(t, list.Revisions, 2) + revs := make([]string, 0, len(list.Revisions)) + for _, rev := range list.Revisions { + revs = append(revs, rev.Revision) + } + assert.ElementsMatch(t, []string{revision1, revision2}, revs) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/packages/%s/revisions", recipeURL, revision1, conanPackageReference)) + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &list) + assert.Len(t, list.Revisions, 2) + revs = make([]string, 0, len(list.Revisions)) + for _, rev := range list.Revisions { + revs = append(revs, rev.Revision) + } + assert.ElementsMatch(t, []string{revision1, revision2}, revs) + }) + + t.Run("Search", func(t *testing.T) { + t.Run("Recipe", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + cases := []struct { + Query string + Expected []string + }{ + {"ConanPackage", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/1.2", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/1.1", []string{}}, + {"Conan*", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/*", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/1*", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/*2", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/1*2", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/1.2@", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"ConanPackage/1.2@du*", []string{"ConanPackage/1.2@dummy/test"}}, + {"ConanPackage/1.2@du*/", []string{"ConanPackage/1.2@dummy/test"}}, + {"ConanPackage/1.2@du*/*test", []string{"ConanPackage/1.2@dummy/test"}}, + {"ConanPackage/1.2@du*/*st", []string{"ConanPackage/1.2@dummy/test"}}, + {"ConanPackage/1.2@gitea/*", []string{"ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}}, + {"*/*@dummy", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test"}}, + {"*/*@*/final", []string{"ConanPackage/1.2@gitea/final"}}, + } + + for i, c := range cases { + req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/conans/search?q=%s", url, stdurl.QueryEscape(c.Query))) + resp := MakeRequest(t, req, http.StatusOK) + + var result *conan_router.SearchResult + DecodeJSON(t, resp, &result) + + assert.ElementsMatch(t, c.Expected, result.Results, "case %d: unexpected result", i) + } + }) + + t.Run("Package", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/search", url, name, version1, user1, channel1)) + resp := MakeRequest(t, req, http.StatusOK) + + var result map[string]*conan_module.Conaninfo + DecodeJSON(t, resp, &result) + + assert.Contains(t, result, conanPackageReference) + info := result[conanPackageReference] + assert.NotEmpty(t, info.Settings) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/search", url, name, version1, user1, channel1, revision1)) + resp = MakeRequest(t, req, http.StatusOK) + + result = make(map[string]*conan_module.Conaninfo) + DecodeJSON(t, resp, &result) + + assert.Contains(t, result, conanPackageReference) + info = result[conanPackageReference] + assert.NotEmpty(t, info.Settings) + }) + }) + + t.Run("Delete", func(t *testing.T) { + t.Run("Package", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + rref, _ := conan_module.NewRecipeReference(name, version1, user1, channel1, revision1) + pref, _ := conan_module.NewPackageReference(rref, conanPackageReference, conan_module.DefaultRevision) + + checkPackageRevisionCount := func(count int) { + revisions, err := conan_model.GetPackageRevisions(db.DefaultContext, user.ID, pref) + assert.NoError(t, err) + assert.Len(t, revisions, count) + } + checkPackageReferenceCount := func(count int) { + references, err := conan_model.GetPackageReferences(db.DefaultContext, user.ID, rref) + assert.NoError(t, err) + assert.Len(t, references, count) + } + + checkPackageRevisionCount(2) + + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s/revisions/%s", url, name, version1, user1, channel1, revision1, conanPackageReference, revision1)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + + checkPackageRevisionCount(1) + + req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s", url, name, version1, user1, channel1, revision1, conanPackageReference)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + + checkPackageRevisionCount(0) + + rref = rref.WithRevision(revision2) + + checkPackageReferenceCount(1) + + req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages", url, name, version1, user1, channel1, revision2)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + + checkPackageReferenceCount(0) + }) + + t.Run("Recipe", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + rref, _ := conan_module.NewRecipeReference(name, version1, user1, channel1, conan_module.DefaultRevision) + + checkRecipeRevisionCount := func(count int) { + revisions, err := conan_model.GetRecipeRevisions(db.DefaultContext, user.ID, rref) + assert.NoError(t, err) + assert.Len(t, revisions, count) + } + + checkRecipeRevisionCount(2) + + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", url, name, version1, user1, channel1, revision1)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + + checkRecipeRevisionCount(1) + + req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + + checkRecipeRevisionCount(0) + }) + }) + }) +} diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go new file mode 100644 index 0000000000..adced5d661 --- /dev/null +++ b/tests/integration/api_packages_container_test.go @@ -0,0 +1,608 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "bytes" + "encoding/base64" + "fmt" + "net/http" + "strings" + "testing" + + "code.gitea.io/gitea/models/db" + packages_model "code.gitea.io/gitea/models/packages" + container_model "code.gitea.io/gitea/models/packages/container" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + container_module "code.gitea.io/gitea/modules/packages/container" + "code.gitea.io/gitea/modules/packages/container/oci" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestPackageContainer(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + has := func(l packages_model.PackagePropertyList, name string) bool { + for _, pp := range l { + if pp.Name == name { + return true + } + } + return false + } + getAllByName := func(l packages_model.PackagePropertyList, name string) []string { + values := make([]string, 0, len(l)) + for _, pp := range l { + if pp.Name == name { + values = append(values, pp.Value) + } + } + return values + } + + images := []string{"test", "te/st"} + tags := []string{"latest", "main"} + multiTag := "multi" + + unknownDigest := "sha256:0000000000000000000000000000000000000000000000000000000000000000" + + blobDigest := "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" + blobContent, _ := base64.StdEncoding.DecodeString(`H4sIAAAJbogA/2IYBaNgFIxYAAgAAP//Lq+17wAEAAA=`) + + configDigest := "sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d" + configContent := `{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/true"],"ArgsEscaped":true,"Image":"sha256:9bd8b88dc68b80cffe126cc820e4b52c6e558eb3b37680bfee8e5f3ed7b8c257"},"container":"b89fe92a887d55c0961f02bdfbfd8ac3ddf66167db374770d2d9e9fab3311510","container_config":{"Hostname":"b89fe92a887d","Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/true\"]"],"ArgsEscaped":true,"Image":"sha256:9bd8b88dc68b80cffe126cc820e4b52c6e558eb3b37680bfee8e5f3ed7b8c257"},"created":"2022-01-01T00:00:00.000000000Z","docker_version":"20.10.12","history":[{"created":"2022-01-01T00:00:00.000000000Z","created_by":"/bin/sh -c #(nop) COPY file:0e7589b0c800daaf6fa460d2677101e4676dd9491980210cb345480e513f3602 in /true "},{"created":"2022-01-01T00:00:00.000000001Z","created_by":"/bin/sh -c #(nop) CMD [\"/true\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:0ff3b91bdf21ecdf2f2f3d4372c2098a14dbe06cd678e8f0a85fd4902d00e2e2"]}}` + + manifestDigest := "sha256:4f10484d1c1bb13e3956b4de1cd42db8e0f14a75be1617b60f2de3cd59c803c6" + manifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeDockerManifest + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` + + untaggedManifestDigest := "sha256:4305f5f5572b9a426b88909b036e52ee3cf3d7b9c1b01fac840e90747f56623d" + untaggedManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageManifest + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` + + indexManifestDigest := "sha256:bab112d6efb9e7f221995caaaa880352feb5bd8b1faf52fae8d12c113aa123ec" + indexManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageIndex + `","manifests":[{"mediaType":"` + oci.MediaTypeDockerManifest + `","digest":"` + manifestDigest + `","platform":{"os":"linux","architecture":"arm","variant":"v7"}},{"mediaType":"` + oci.MediaTypeImageManifest + `","digest":"` + untaggedManifestDigest + `","platform":{"os":"linux","architecture":"arm64","variant":"v8"}}]}` + + anonymousToken := "" + userToken := "" + + t.Run("Authenticate", func(t *testing.T) { + type TokenResponse struct { + Token string `json:"token"` + } + + authenticate := []string{`Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*"`} + + t.Run("Anonymous", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)) + resp := MakeRequest(t, req, http.StatusUnauthorized) + + assert.ElementsMatch(t, authenticate, resp.Header().Values("WWW-Authenticate")) + + req = NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL)) + resp = MakeRequest(t, req, http.StatusOK) + + tokenResponse := &TokenResponse{} + DecodeJSON(t, resp, &tokenResponse) + + assert.NotEmpty(t, tokenResponse.Token) + + anonymousToken = fmt.Sprintf("Bearer %s", tokenResponse.Token) + + req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)) + addTokenAuthHeader(req, anonymousToken) + MakeRequest(t, req, http.StatusOK) + }) + + t.Run("User", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)) + resp := MakeRequest(t, req, http.StatusUnauthorized) + + assert.ElementsMatch(t, authenticate, resp.Header().Values("WWW-Authenticate")) + + req = NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL)) + req = AddBasicAuthHeader(req, user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + tokenResponse := &TokenResponse{} + DecodeJSON(t, resp, &tokenResponse) + + assert.NotEmpty(t, tokenResponse.Token) + + userToken = fmt.Sprintf("Bearer %s", tokenResponse.Token) + + req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusOK) + }) + }) + + t.Run("DetermineSupport", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)) + addTokenAuthHeader(req, userToken) + resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "registry/2.0", resp.Header().Get("Docker-Distribution-Api-Version")) + }) + + for _, image := range images { + t.Run(fmt.Sprintf("[Image:%s]", image), func(t *testing.T) { + url := fmt.Sprintf("%sv2/%s/%s", setting.AppURL, user.Name, image) + + t.Run("UploadBlob/Monolithic", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)) + addTokenAuthHeader(req, anonymousToken) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, unknownDigest), bytes.NewReader(blobContent)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusBadRequest) + + req = NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, blobDigest), bytes.NewReader(blobContent)) + addTokenAuthHeader(req, userToken) + resp := MakeRequest(t, req, http.StatusCreated) + + assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location")) + assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest")) + + pv, err := packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, container_model.UploadVersion) + assert.NoError(t, err) + + pfs, err := packages_model.GetFilesByVersionID(db.DefaultContext, pv.ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + + pb, err := packages_model.GetBlobByID(db.DefaultContext, pfs[0].BlobID) + assert.NoError(t, err) + assert.EqualValues(t, len(blobContent), pb.Size) + }) + + t.Run("UploadBlob/Chunked", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)) + addTokenAuthHeader(req, userToken) + resp := MakeRequest(t, req, http.StatusAccepted) + + uuid := resp.Header().Get("Docker-Upload-Uuid") + assert.NotEmpty(t, uuid) + + pbu, err := packages_model.GetBlobUploadByID(db.DefaultContext, uuid) + assert.NoError(t, err) + assert.EqualValues(t, 0, pbu.BytesReceived) + + uploadURL := resp.Header().Get("Location") + assert.NotEmpty(t, uploadURL) + + req = NewRequestWithBody(t, "PATCH", setting.AppURL+uploadURL[1:]+"000", bytes.NewReader(blobContent)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequestWithBody(t, "PATCH", setting.AppURL+uploadURL[1:], bytes.NewReader(blobContent)) + addTokenAuthHeader(req, userToken) + + req.Header.Set("Content-Range", "1-10") + MakeRequest(t, req, http.StatusRequestedRangeNotSatisfiable) + + contentRange := fmt.Sprintf("0-%d", len(blobContent)-1) + req.Header.Set("Content-Range", contentRange) + resp = MakeRequest(t, req, http.StatusAccepted) + + assert.Equal(t, uuid, resp.Header().Get("Docker-Upload-Uuid")) + assert.Equal(t, contentRange, resp.Header().Get("Range")) + + pbu, err = packages_model.GetBlobUploadByID(db.DefaultContext, uuid) + assert.NoError(t, err) + assert.EqualValues(t, len(blobContent), pbu.BytesReceived) + + uploadURL = resp.Header().Get("Location") + + req = NewRequest(t, "PUT", fmt.Sprintf("%s?digest=%s", setting.AppURL+uploadURL[1:], blobDigest)) + addTokenAuthHeader(req, userToken) + resp = MakeRequest(t, req, http.StatusCreated) + + assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location")) + assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest")) + }) + + for _, tag := range tags { + t.Run(fmt.Sprintf("[Tag:%s]", tag), func(t *testing.T) { + t.Run("UploadManifest", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, configDigest), strings.NewReader(configContent)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/manifests/%s", url, tag), strings.NewReader(manifestContent)) + addTokenAuthHeader(req, anonymousToken) + req.Header.Set("Content-Type", oci.MediaTypeDockerManifest) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/manifests/%s", url, tag), strings.NewReader(manifestContent)) + addTokenAuthHeader(req, userToken) + req.Header.Set("Content-Type", oci.MediaTypeDockerManifest) + resp := MakeRequest(t, req, http.StatusCreated) + + assert.Equal(t, manifestDigest, resp.Header().Get("Docker-Content-Digest")) + + pv, err := packages_model.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, tag) + assert.NoError(t, err) + + pd, err := packages_model.GetPackageDescriptor(db.DefaultContext, pv) + assert.NoError(t, err) + assert.Nil(t, pd.SemVer) + assert.Equal(t, image, pd.Package.Name) + assert.Equal(t, tag, pd.Version.Version) + assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository)) + assert.True(t, has(pd.VersionProperties, container_module.PropertyManifestTagged)) + + assert.IsType(t, &container_module.Metadata{}, pd.Metadata) + metadata := pd.Metadata.(*container_module.Metadata) + assert.Equal(t, container_module.TypeOCI, metadata.Type) + assert.Len(t, metadata.ImageLayers, 2) + assert.Empty(t, metadata.MultiArch) + + assert.Len(t, pd.Files, 3) + for _, pfd := range pd.Files { + switch pfd.File.Name { + case container_model.ManifestFilename: + assert.True(t, pfd.File.IsLead) + assert.Equal(t, oci.MediaTypeDockerManifest, pfd.Properties.GetByName(container_module.PropertyMediaType)) + assert.Equal(t, manifestDigest, pfd.Properties.GetByName(container_module.PropertyDigest)) + case strings.Replace(configDigest, ":", "_", 1): + assert.False(t, pfd.File.IsLead) + assert.Equal(t, "application/vnd.docker.container.image.v1+json", pfd.Properties.GetByName(container_module.PropertyMediaType)) + assert.Equal(t, configDigest, pfd.Properties.GetByName(container_module.PropertyDigest)) + case strings.Replace(blobDigest, ":", "_", 1): + assert.False(t, pfd.File.IsLead) + assert.Equal(t, "application/vnd.docker.image.rootfs.diff.tar.gzip", pfd.Properties.GetByName(container_module.PropertyMediaType)) + assert.Equal(t, blobDigest, pfd.Properties.GetByName(container_module.PropertyDigest)) + default: + assert.Fail(t, "unknown file: %s", pfd.File.Name) + } + } + + req = NewRequest(t, "GET", fmt.Sprintf("%s/manifests/%s", url, tag)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusOK) + + pv, err = packages_model.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, tag) + assert.NoError(t, err) + assert.EqualValues(t, 1, pv.DownloadCount) + + // Overwrite existing tag should keep the download count + req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/manifests/%s", url, tag), strings.NewReader(manifestContent)) + addTokenAuthHeader(req, userToken) + req.Header.Set("Content-Type", oci.MediaTypeDockerManifest) + MakeRequest(t, req, http.StatusCreated) + + pv, err = packages_model.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, tag) + assert.NoError(t, err) + assert.EqualValues(t, 1, pv.DownloadCount) + }) + + t.Run("HeadManifest", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "HEAD", fmt.Sprintf("%s/manifests/unknown-tag", url)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "HEAD", fmt.Sprintf("%s/manifests/%s", url, tag)) + addTokenAuthHeader(req, userToken) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, fmt.Sprintf("%d", len(manifestContent)), resp.Header().Get("Content-Length")) + assert.Equal(t, manifestDigest, resp.Header().Get("Docker-Content-Digest")) + }) + + t.Run("GetManifest", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/manifests/unknown-tag", url)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/manifests/%s", url, tag)) + addTokenAuthHeader(req, userToken) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, fmt.Sprintf("%d", len(manifestContent)), resp.Header().Get("Content-Length")) + assert.Equal(t, oci.MediaTypeDockerManifest, resp.Header().Get("Content-Type")) + assert.Equal(t, manifestDigest, resp.Header().Get("Docker-Content-Digest")) + assert.Equal(t, manifestContent, resp.Body.String()) + }) + }) + } + + t.Run("UploadUntaggedManifest", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/manifests/%s", url, untaggedManifestDigest), strings.NewReader(untaggedManifestContent)) + addTokenAuthHeader(req, userToken) + req.Header.Set("Content-Type", oci.MediaTypeImageManifest) + resp := MakeRequest(t, req, http.StatusCreated) + + assert.Equal(t, untaggedManifestDigest, resp.Header().Get("Docker-Content-Digest")) + + req = NewRequest(t, "HEAD", fmt.Sprintf("%s/manifests/%s", url, untaggedManifestDigest)) + addTokenAuthHeader(req, userToken) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, fmt.Sprintf("%d", len(untaggedManifestContent)), resp.Header().Get("Content-Length")) + assert.Equal(t, untaggedManifestDigest, resp.Header().Get("Docker-Content-Digest")) + + pv, err := packages_model.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, untaggedManifestDigest) + assert.NoError(t, err) + + pd, err := packages_model.GetPackageDescriptor(db.DefaultContext, pv) + assert.NoError(t, err) + assert.Nil(t, pd.SemVer) + assert.Equal(t, image, pd.Package.Name) + assert.Equal(t, untaggedManifestDigest, pd.Version.Version) + assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository)) + assert.False(t, has(pd.VersionProperties, container_module.PropertyManifestTagged)) + + assert.IsType(t, &container_module.Metadata{}, pd.Metadata) + + assert.Len(t, pd.Files, 3) + for _, pfd := range pd.Files { + if pfd.File.Name == container_model.ManifestFilename { + assert.True(t, pfd.File.IsLead) + assert.Equal(t, oci.MediaTypeImageManifest, pfd.Properties.GetByName(container_module.PropertyMediaType)) + assert.Equal(t, untaggedManifestDigest, pfd.Properties.GetByName(container_module.PropertyDigest)) + } + } + }) + + t.Run("UploadIndexManifest", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/manifests/%s", url, multiTag), strings.NewReader(indexManifestContent)) + addTokenAuthHeader(req, userToken) + req.Header.Set("Content-Type", oci.MediaTypeImageIndex) + resp := MakeRequest(t, req, http.StatusCreated) + + assert.Equal(t, indexManifestDigest, resp.Header().Get("Docker-Content-Digest")) + + pv, err := packages_model.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, multiTag) + assert.NoError(t, err) + + pd, err := packages_model.GetPackageDescriptor(db.DefaultContext, pv) + assert.NoError(t, err) + assert.Nil(t, pd.SemVer) + assert.Equal(t, image, pd.Package.Name) + assert.Equal(t, multiTag, pd.Version.Version) + assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository)) + assert.True(t, has(pd.VersionProperties, container_module.PropertyManifestTagged)) + + assert.ElementsMatch(t, []string{manifestDigest, untaggedManifestDigest}, getAllByName(pd.VersionProperties, container_module.PropertyManifestReference)) + + assert.IsType(t, &container_module.Metadata{}, pd.Metadata) + metadata := pd.Metadata.(*container_module.Metadata) + assert.Equal(t, container_module.TypeOCI, metadata.Type) + assert.Contains(t, metadata.MultiArch, "linux/arm/v7") + assert.Equal(t, manifestDigest, metadata.MultiArch["linux/arm/v7"]) + assert.Contains(t, metadata.MultiArch, "linux/arm64/v8") + assert.Equal(t, untaggedManifestDigest, metadata.MultiArch["linux/arm64/v8"]) + + assert.Len(t, pd.Files, 1) + assert.True(t, pd.Files[0].File.IsLead) + assert.Equal(t, oci.MediaTypeImageIndex, pd.Files[0].Properties.GetByName(container_module.PropertyMediaType)) + assert.Equal(t, indexManifestDigest, pd.Files[0].Properties.GetByName(container_module.PropertyDigest)) + }) + + t.Run("UploadBlob/Mount", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, unknownDigest)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusAccepted) + + req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, blobDigest)) + addTokenAuthHeader(req, userToken) + resp := MakeRequest(t, req, http.StatusCreated) + + assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location")) + assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest")) + }) + + t.Run("HeadBlob", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "HEAD", fmt.Sprintf("%s/blobs/%s", url, unknownDigest)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "HEAD", fmt.Sprintf("%s/blobs/%s", url, blobDigest)) + addTokenAuthHeader(req, userToken) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, fmt.Sprintf("%d", len(blobContent)), resp.Header().Get("Content-Length")) + assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest")) + }) + + t.Run("GetBlob", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/blobs/%s", url, unknownDigest)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/blobs/%s", url, blobDigest)) + addTokenAuthHeader(req, userToken) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, fmt.Sprintf("%d", len(blobContent)), resp.Header().Get("Content-Length")) + assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest")) + assert.Equal(t, blobContent, resp.Body.Bytes()) + }) + + t.Run("GetTagList", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + cases := []struct { + URL string + ExpectedTags []string + ExpectedLink string + }{ + { + URL: fmt.Sprintf("%s/tags/list", url), + ExpectedTags: []string{"latest", "main", "multi"}, + ExpectedLink: fmt.Sprintf(`</v2/%s/%s/tags/list?last=multi>; rel="next"`, user.Name, image), + }, + { + URL: fmt.Sprintf("%s/tags/list?n=0", url), + ExpectedTags: []string{}, + ExpectedLink: "", + }, + { + URL: fmt.Sprintf("%s/tags/list?n=2", url), + ExpectedTags: []string{"latest", "main"}, + ExpectedLink: fmt.Sprintf(`</v2/%s/%s/tags/list?last=main&n=2>; rel="next"`, user.Name, image), + }, + { + URL: fmt.Sprintf("%s/tags/list?last=main", url), + ExpectedTags: []string{"multi"}, + ExpectedLink: fmt.Sprintf(`</v2/%s/%s/tags/list?last=multi>; rel="next"`, user.Name, image), + }, + { + URL: fmt.Sprintf("%s/tags/list?n=1&last=latest", url), + ExpectedTags: []string{"main"}, + ExpectedLink: fmt.Sprintf(`</v2/%s/%s/tags/list?last=main&n=1>; rel="next"`, user.Name, image), + }, + } + + for _, c := range cases { + req := NewRequest(t, "GET", c.URL) + addTokenAuthHeader(req, userToken) + resp := MakeRequest(t, req, http.StatusOK) + + type TagList struct { + Name string `json:"name"` + Tags []string `json:"tags"` + } + + tagList := &TagList{} + DecodeJSON(t, resp, &tagList) + + assert.Equal(t, user.Name+"/"+image, tagList.Name) + assert.Equal(t, c.ExpectedTags, tagList.Tags) + assert.Equal(t, c.ExpectedLink, resp.Header().Get("Link")) + } + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s?type=container&q=%s", user.Name, image)) + resp := MakeRequest(t, req, http.StatusOK) + + var apiPackages []*api.Package + DecodeJSON(t, resp, &apiPackages) + assert.Len(t, apiPackages, 4) // "latest", "main", "multi", "sha256:..." + }) + + t.Run("Delete", func(t *testing.T) { + t.Run("Blob", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/blobs/%s", url, blobDigest)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusAccepted) + + req = NewRequest(t, "HEAD", fmt.Sprintf("%s/blobs/%s", url, blobDigest)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("ManifestByDigest", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/manifests/%s", url, untaggedManifestDigest)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusAccepted) + + req = NewRequest(t, "HEAD", fmt.Sprintf("%s/manifests/%s", url, untaggedManifestDigest)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("ManifestByTag", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/manifests/%s", url, multiTag)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusAccepted) + + req = NewRequest(t, "HEAD", fmt.Sprintf("%s/manifests/%s", url, multiTag)) + addTokenAuthHeader(req, userToken) + MakeRequest(t, req, http.StatusNotFound) + }) + }) + }) + } + + t.Run("OwnerNameChange", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + checkCatalog := func(owner string) func(t *testing.T) { + return func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%sv2/_catalog", setting.AppURL)) + addTokenAuthHeader(req, userToken) + resp := MakeRequest(t, req, http.StatusOK) + + type RepositoryList struct { + Repositories []string `json:"repositories"` + } + + repoList := &RepositoryList{} + DecodeJSON(t, resp, &repoList) + + assert.Len(t, repoList.Repositories, len(images)) + names := make([]string, 0, len(images)) + for _, image := range images { + names = append(names, strings.ToLower(owner+"/"+image)) + } + assert.ElementsMatch(t, names, repoList.Repositories) + } + } + + t.Run(fmt.Sprintf("Catalog[%s]", user.LowerName), checkCatalog(user.LowerName)) + + session := loginUser(t, user.Name) + + newOwnerName := "newUsername" + + req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ + "_csrf": GetCSRF(t, session, "/user/settings"), + "name": newOwnerName, + "email": "user2@example.com", + "language": "en-US", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + t.Run(fmt.Sprintf("Catalog[%s]", newOwnerName), checkCatalog(newOwnerName)) + + req = NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ + "_csrf": GetCSRF(t, session, "/user/settings"), + "name": user.Name, + "email": "user2@example.com", + "language": "en-US", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + }) +} diff --git a/tests/integration/api_packages_generic_test.go b/tests/integration/api_packages_generic_test.go new file mode 100644 index 0000000000..9fcd2cc797 --- /dev/null +++ b/tests/integration/api_packages_generic_test.go @@ -0,0 +1,194 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "bytes" + "fmt" + "net/http" + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestPackageGeneric(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + packageName := "te-st_pac.kage" + packageVersion := "1.0.3-te st" + filename := "fi-le_na.me" + content := []byte{1, 2, 3} + + url := fmt.Sprintf("/api/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", url+"/"+filename, bytes.NewReader(content)) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusCreated) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.Nil(t, pd.Metadata) + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + assert.Equal(t, filename, pfs[0].Name) + assert.True(t, pfs[0].IsLead) + + pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(len(content)), pb.Size) + + t.Run("Exists", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", url+"/"+filename, bytes.NewReader(content)) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusConflict) + }) + + t.Run("Additional", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", url+"/dummy.bin", bytes.NewReader(content)) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusCreated) + + // Check deduplication + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 2) + assert.Equal(t, pfs[0].BlobID, pfs[1].BlobID) + }) + + t.Run("InvalidParameter", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, "invalid+package name", packageVersion, filename), bytes.NewReader(content)) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusBadRequest) + + req = NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, "%20test ", filename), bytes.NewReader(content)) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusBadRequest) + + req = NewRequestWithBody(t, "PUT", fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, packageVersion, "inval+id.na me"), bytes.NewReader(content)) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusBadRequest) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + checkDownloadCount := func(count int64) { + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + assert.Equal(t, count, pvs[0].DownloadCount) + } + + checkDownloadCount(0) + + req := NewRequest(t, "GET", url+"/"+filename) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, content, resp.Body.Bytes()) + + checkDownloadCount(1) + + req = NewRequest(t, "GET", url+"/dummy.bin") + MakeRequest(t, req, http.StatusOK) + + checkDownloadCount(2) + + t.Run("NotExists", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", url+"/not.found") + MakeRequest(t, req, http.StatusNotFound) + }) + }) + + t.Run("Delete", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("File", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "DELETE", url+"/"+filename) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "DELETE", url+"/"+filename) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "GET", url+"/"+filename) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "DELETE", url+"/"+filename) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNotFound) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + t.Run("RemovesVersion", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req = NewRequest(t, "DELETE", url+"/dummy.bin") + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNoContent) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric) + assert.NoError(t, err) + assert.Empty(t, pvs) + }) + }) + + t.Run("Version", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", url+"/"+filename, bytes.NewReader(content)) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "DELETE", url) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "DELETE", url) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNoContent) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric) + assert.NoError(t, err) + assert.Empty(t, pvs) + + req = NewRequest(t, "GET", url+"/"+filename) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "DELETE", url) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNotFound) + }) + }) +} diff --git a/tests/integration/api_packages_helm_test.go b/tests/integration/api_packages_helm_test.go new file mode 100644 index 0000000000..393bf3cbe2 --- /dev/null +++ b/tests/integration/api_packages_helm_test.go @@ -0,0 +1,167 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "fmt" + "net/http" + "testing" + "time" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + helm_module "code.gitea.io/gitea/modules/packages/helm" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" +) + +func TestPackageHelm(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + packageName := "test-chart" + packageVersion := "1.0.3" + packageAuthor := "KN4CK3R" + packageDescription := "Gitea Test Package" + + filename := fmt.Sprintf("%s-%s.tgz", packageName, packageVersion) + + chartContent := `apiVersion: v2 +description: ` + packageDescription + ` +name: ` + packageName + ` +type: application +version: ` + packageVersion + ` +maintainers: +- name: ` + packageAuthor + ` +dependencies: +- name: dep1 + repository: https://example.com/ + version: 1.0.0` + + var buf bytes.Buffer + zw := gzip.NewWriter(&buf) + archive := tar.NewWriter(zw) + archive.WriteHeader(&tar.Header{ + Name: fmt.Sprintf("%s/Chart.yaml", packageName), + Mode: 0o600, + Size: int64(len(chartContent)), + }) + archive.Write([]byte(chartContent)) + archive.Close() + zw.Close() + content := buf.Bytes() + + url := fmt.Sprintf("/api/packages/%s/helm", user.Name) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + uploadURL := url + "/api/charts" + + req := NewRequestWithBody(t, "POST", uploadURL, bytes.NewReader(content)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusCreated) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeHelm) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.IsType(t, &helm_module.Metadata{}, pd.Metadata) + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + assert.Equal(t, filename, pfs[0].Name) + assert.True(t, pfs[0].IsLead) + + pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(len(content)), pb.Size) + + req = NewRequestWithBody(t, "POST", uploadURL, bytes.NewReader(content)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusCreated) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + checkDownloadCount := func(count int64) { + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeHelm) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + assert.Equal(t, count, pvs[0].DownloadCount) + } + + checkDownloadCount(0) + + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s", url, filename)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, content, resp.Body.Bytes()) + + checkDownloadCount(1) + }) + + t.Run("Index", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/index.yaml", url)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + type ChartVersion struct { + helm_module.Metadata `yaml:",inline"` + URLs []string `yaml:"urls"` + Created time.Time `yaml:"created,omitempty"` + Removed bool `yaml:"removed,omitempty"` + Digest string `yaml:"digest,omitempty"` + } + + type ServerInfo struct { + ContextPath string `yaml:"contextPath,omitempty"` + } + + type Index struct { + APIVersion string `yaml:"apiVersion"` + Entries map[string][]*ChartVersion `yaml:"entries"` + Generated time.Time `yaml:"generated,omitempty"` + ServerInfo *ServerInfo `yaml:"serverInfo,omitempty"` + } + + var result Index + assert.NoError(t, yaml.NewDecoder(resp.Body).Decode(&result)) + assert.NotEmpty(t, result.Entries) + assert.Contains(t, result.Entries, packageName) + + cvs := result.Entries[packageName] + assert.Len(t, cvs, 1) + + cv := cvs[0] + assert.Equal(t, packageName, cv.Name) + assert.Equal(t, packageVersion, cv.Version) + assert.Equal(t, packageDescription, cv.Description) + assert.Len(t, cv.Maintainers, 1) + assert.Equal(t, packageAuthor, cv.Maintainers[0].Name) + assert.Len(t, cv.Dependencies, 1) + assert.ElementsMatch(t, []string{fmt.Sprintf("%s%s/%s", setting.AppURL, url[1:], filename)}, cv.URLs) + + assert.Equal(t, url, result.ServerInfo.ContextPath) + }) +} diff --git a/tests/integration/api_packages_maven_test.go b/tests/integration/api_packages_maven_test.go new file mode 100644 index 0000000000..87d95557ce --- /dev/null +++ b/tests/integration/api_packages_maven_test.go @@ -0,0 +1,218 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/packages/maven" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestPackageMaven(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + groupID := "com.gitea" + artifactID := "test-project" + packageName := groupID + "-" + artifactID + packageVersion := "1.0.1" + packageDescription := "Test Description" + + root := fmt.Sprintf("/api/packages/%s/maven/%s/%s", user.Name, strings.ReplaceAll(groupID, ".", "/"), artifactID) + filename := fmt.Sprintf("%s-%s.jar", packageName, packageVersion) + + putFile := func(t *testing.T, path, content string, expectedStatus int) { + req := NewRequestWithBody(t, "PUT", root+path, strings.NewReader(content)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, expectedStatus) + } + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + putFile(t, fmt.Sprintf("/%s/%s", packageVersion, filename), "test", http.StatusCreated) + putFile(t, fmt.Sprintf("/%s/%s", packageVersion, filename), "test", http.StatusBadRequest) + putFile(t, "/maven-metadata.xml", "test", http.StatusOK) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeMaven) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.Nil(t, pd.SemVer) + assert.Nil(t, pd.Metadata) + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + assert.Equal(t, filename, pfs[0].Name) + assert.False(t, pfs[0].IsLead) + + pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(4), pb.Size) + }) + + t.Run("UploadExists", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + putFile(t, fmt.Sprintf("/%s/%s", packageVersion, filename), "test", http.StatusBadRequest) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s", root, packageVersion, filename)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, []byte("test"), resp.Body.Bytes()) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeMaven) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + assert.Equal(t, int64(0), pvs[0].DownloadCount) + }) + + t.Run("UploadVerifySHA1", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("Missmatch", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + putFile(t, fmt.Sprintf("/%s/%s.sha1", packageVersion, filename), "test", http.StatusBadRequest) + }) + t.Run("Valid", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + putFile(t, fmt.Sprintf("/%s/%s.sha1", packageVersion, filename), "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", http.StatusOK) + }) + }) + + pomContent := `<?xml version="1.0"?> +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <groupId>` + groupID + `</groupId> + <artifactId>` + artifactID + `</artifactId> + <version>` + packageVersion + `</version> + <description>` + packageDescription + `</description> +</project>` + + t.Run("UploadPOM", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeMaven) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.Nil(t, pd.Metadata) + + putFile(t, fmt.Sprintf("/%s/%s.pom", packageVersion, filename), pomContent, http.StatusCreated) + + pvs, err = packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeMaven) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err = packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.IsType(t, &maven.Metadata{}, pd.Metadata) + assert.Equal(t, packageDescription, pd.Metadata.(*maven.Metadata).Description) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 2) + for _, pf := range pfs { + if strings.HasSuffix(pf.Name, ".pom") { + assert.Equal(t, filename+".pom", pf.Name) + assert.True(t, pf.IsLead) + } else { + assert.False(t, pf.IsLead) + } + } + }) + + t.Run("DownloadPOM", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s.pom", root, packageVersion, filename)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, []byte(pomContent), resp.Body.Bytes()) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeMaven) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + assert.Equal(t, int64(1), pvs[0].DownloadCount) + }) + + t.Run("DownloadChecksums", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/1.2.3/%s", root, filename)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNotFound) + + for key, checksum := range map[string]string{ + "md5": "098f6bcd4621d373cade4e832627b4f6", + "sha1": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", + "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", + "sha512": "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", + } { + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s.%s", root, packageVersion, filename, key)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, checksum, resp.Body.String()) + } + }) + + t.Run("DownloadMetadata", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", root+"/maven-metadata.xml") + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + expectedMetadata := `<?xml version="1.0" encoding="UTF-8"?>` + "\n<metadata><groupId>com.gitea</groupId><artifactId>test-project</artifactId><versioning><release>1.0.1</release><latest>1.0.1</latest><versions><version>1.0.1</version></versions></versioning></metadata>" + assert.Equal(t, expectedMetadata, resp.Body.String()) + + for key, checksum := range map[string]string{ + "md5": "6bee0cebaaa686d658adf3e7e16371a0", + "sha1": "8696abce499fe84d9ea93e5492abe7147e195b6c", + "sha256": "3f48322f81c4b2c3bb8649ae1e5c9801476162b520e1c2734ac06b2c06143208", + "sha512": "cb075aa2e2ef1a83cdc14dd1e08c505b72d633399b39e73a21f00f0deecb39a3e2c79f157c1163f8a3854828750706e0dec3a0f5e4778e91f8ec2cf351a855f2", + } { + req := NewRequest(t, "GET", fmt.Sprintf("%s/maven-metadata.xml.%s", root, key)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, checksum, resp.Body.String()) + } + }) + + t.Run("UploadSnapshot", func(t *testing.T) { + snapshotVersion := packageVersion + "-SNAPSHOT" + + putFile(t, fmt.Sprintf("/%s/%s", snapshotVersion, filename), "test", http.StatusCreated) + putFile(t, "/maven-metadata.xml", "test", http.StatusOK) + putFile(t, fmt.Sprintf("/%s/maven-metadata.xml", snapshotVersion), "test", http.StatusCreated) + putFile(t, fmt.Sprintf("/%s/maven-metadata.xml", snapshotVersion), "test-overwrite", http.StatusCreated) + }) +} diff --git a/tests/integration/api_packages_npm_test.go b/tests/integration/api_packages_npm_test.go new file mode 100644 index 0000000000..fe6cea1cb6 --- /dev/null +++ b/tests/integration/api_packages_npm_test.go @@ -0,0 +1,279 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "encoding/base64" + "fmt" + "net/http" + "net/url" + "strings" + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/packages/npm" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestPackageNpm(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + token := fmt.Sprintf("Bearer %s", getTokenForLoggedInUser(t, loginUser(t, user.Name))) + + packageName := "@scope/test-package" + packageVersion := "1.0.1-pre" + packageTag := "latest" + packageTag2 := "release" + packageAuthor := "KN4CK3R" + packageDescription := "Test Description" + + data := "H4sIAAAAAAAA/ytITM5OTE/VL4DQelnF+XkMVAYGBgZmJiYK2MRBwNDcSIHB2NTMwNDQzMwAqA7IMDUxA9LUdgg2UFpcklgEdAql5kD8ogCnhwio5lJQUMpLzE1VslJQcihOzi9I1S9JLS7RhSYIJR2QgrLUouLM/DyQGkM9Az1D3YIiqExKanFyUWZBCVQ2BKhVwQVJDKwosbQkI78IJO/tZ+LsbRykxFXLNdA+HwWjYBSMgpENACgAbtAACAAA" + + buildUpload := func(version string) string { + return `{ + "_id": "` + packageName + `", + "name": "` + packageName + `", + "description": "` + packageDescription + `", + "dist-tags": { + "` + packageTag + `": "` + version + `" + }, + "versions": { + "` + version + `": { + "name": "` + packageName + `", + "version": "` + version + `", + "description": "` + packageDescription + `", + "author": { + "name": "` + packageAuthor + `" + }, + "dist": { + "integrity": "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg==", + "shasum": "aaa7eaf852a948b0aa05afeda35b1badca155d90" + } + } + }, + "_attachments": { + "` + packageName + `-` + version + `.tgz": { + "data": "` + data + `" + } + } + }` + } + + root := fmt.Sprintf("/api/packages/%s/npm/%s", user.Name, url.QueryEscape(packageName)) + tagsRoot := fmt.Sprintf("/api/packages/%s/npm/-/package/%s/dist-tags", user.Name, url.QueryEscape(packageName)) + filename := fmt.Sprintf("%s-%s.tgz", strings.Split(packageName, "/")[1], packageVersion) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", root, strings.NewReader(buildUpload(packageVersion))) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusCreated) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.IsType(t, &npm.Metadata{}, pd.Metadata) + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) + assert.Len(t, pd.VersionProperties, 1) + assert.Equal(t, npm.TagProperty, pd.VersionProperties[0].Name) + assert.Equal(t, packageTag, pd.VersionProperties[0].Value) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + assert.Equal(t, filename, pfs[0].Name) + assert.True(t, pfs[0].IsLead) + + pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(192), pb.Size) + }) + + t.Run("UploadExists", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", root, strings.NewReader(buildUpload(packageVersion))) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusBadRequest) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/-/%s/%s", root, packageVersion, filename)) + req = addTokenAuthHeader(req, token) + resp := MakeRequest(t, req, http.StatusOK) + + b, _ := base64.StdEncoding.DecodeString(data) + assert.Equal(t, b, resp.Body.Bytes()) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + assert.Equal(t, int64(1), pvs[0].DownloadCount) + }) + + t.Run("PackageMetadata", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("/api/packages/%s/npm/%s", user.Name, "does-not-exist")) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", root) + req = addTokenAuthHeader(req, token) + resp := MakeRequest(t, req, http.StatusOK) + + var result npm.PackageMetadata + DecodeJSON(t, resp, &result) + + assert.Equal(t, packageName, result.ID) + assert.Equal(t, packageName, result.Name) + assert.Equal(t, packageDescription, result.Description) + assert.Contains(t, result.DistTags, packageTag) + assert.Equal(t, packageVersion, result.DistTags[packageTag]) + assert.Equal(t, packageAuthor, result.Author.Name) + assert.Contains(t, result.Versions, packageVersion) + pmv := result.Versions[packageVersion] + assert.Equal(t, fmt.Sprintf("%s@%s", packageName, packageVersion), pmv.ID) + assert.Equal(t, packageName, pmv.Name) + assert.Equal(t, packageDescription, pmv.Description) + assert.Equal(t, packageAuthor, pmv.Author.Name) + assert.Equal(t, "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg==", pmv.Dist.Integrity) + assert.Equal(t, "aaa7eaf852a948b0aa05afeda35b1badca155d90", pmv.Dist.Shasum) + assert.Equal(t, fmt.Sprintf("%s%s/-/%s/%s", setting.AppURL, root[1:], packageVersion, filename), pmv.Dist.Tarball) + }) + + t.Run("AddTag", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + test := func(t *testing.T, status int, tag, version string) { + req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/%s", tagsRoot, tag), strings.NewReader(`"`+version+`"`)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, status) + } + + test(t, http.StatusBadRequest, "1.0", packageVersion) + test(t, http.StatusBadRequest, "v1.0", packageVersion) + test(t, http.StatusNotFound, packageTag2, "1.2") + test(t, http.StatusOK, packageTag, packageVersion) + test(t, http.StatusOK, packageTag2, packageVersion) + }) + + t.Run("ListTags", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", tagsRoot) + req = addTokenAuthHeader(req, token) + resp := MakeRequest(t, req, http.StatusOK) + + var result map[string]string + DecodeJSON(t, resp, &result) + + assert.Len(t, result, 2) + assert.Contains(t, result, packageTag) + assert.Equal(t, packageVersion, result[packageTag]) + assert.Contains(t, result, packageTag2) + assert.Equal(t, packageVersion, result[packageTag2]) + }) + + t.Run("PackageMetadataDistTags", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", root) + req = addTokenAuthHeader(req, token) + resp := MakeRequest(t, req, http.StatusOK) + + var result npm.PackageMetadata + DecodeJSON(t, resp, &result) + + assert.Len(t, result.DistTags, 2) + assert.Contains(t, result.DistTags, packageTag) + assert.Equal(t, packageVersion, result.DistTags[packageTag]) + assert.Contains(t, result.DistTags, packageTag2) + assert.Equal(t, packageVersion, result.DistTags[packageTag2]) + }) + + t.Run("DeleteTag", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + test := func(t *testing.T, status int, tag string) { + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s", tagsRoot, tag)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, status) + } + + test(t, http.StatusBadRequest, "v1.0") + test(t, http.StatusBadRequest, "1.0") + test(t, http.StatusOK, "dummy") + test(t, http.StatusOK, packageTag2) + }) + + t.Run("Delete", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", root, strings.NewReader(buildUpload(packageVersion+"-dummy"))) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "PUT", root+"/-rev/dummy") + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "PUT", root+"/-rev/dummy") + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + + t.Run("Version", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm) + assert.NoError(t, err) + assert.Len(t, pvs, 2) + + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/-/%s/%s/-rev/dummy", root, packageVersion, filename)) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "DELETE", fmt.Sprintf("%s/-/%s/%s/-rev/dummy", root, packageVersion, filename)) + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + + pvs, err = packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + }) + + t.Run("Full", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + req := NewRequest(t, "DELETE", root+"/-rev/dummy") + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "DELETE", root+"/-rev/dummy") + req = addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + + pvs, err = packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm) + assert.NoError(t, err) + assert.Len(t, pvs, 0) + }) + }) +} diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go new file mode 100644 index 0000000000..87275feb3e --- /dev/null +++ b/tests/integration/api_packages_nuget_test.go @@ -0,0 +1,393 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "archive/zip" + "bytes" + "encoding/base64" + "fmt" + "io" + "net/http" + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + nuget_module "code.gitea.io/gitea/modules/packages/nuget" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/routers/api/packages/nuget" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func addNuGetAPIKeyHeader(request *http.Request, token string) *http.Request { + request.Header.Set("X-NuGet-ApiKey", token) + return request +} + +func TestPackageNuGet(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + token := getUserToken(t, user.Name) + + packageName := "test.package" + packageVersion := "1.0.3" + packageAuthors := "KN4CK3R" + packageDescription := "Gitea Test Package" + symbolFilename := "test.pdb" + symbolID := "d910bb6948bd4c6cb40155bcf52c3c94" + + var buf bytes.Buffer + archive := zip.NewWriter(&buf) + w, _ := archive.Create("package.nuspec") + w.Write([]byte(`<?xml version="1.0" encoding="utf-8"?> + <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> + <metadata> + <id>` + packageName + `</id> + <version>` + packageVersion + `</version> + <authors>` + packageAuthors + `</authors> + <description>` + packageDescription + `</description> + <group targetFramework=".NETStandard2.0"> + <dependency id="Microsoft.CSharp" version="4.5.0" /> + </group> + </metadata> + </package>`)) + archive.Close() + content := buf.Bytes() + + url := fmt.Sprintf("/api/packages/%s/nuget", user.Name) + + t.Run("ServiceIndex", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url)) + req = addNuGetAPIKeyHeader(req, token) + resp := MakeRequest(t, req, http.StatusOK) + + var result nuget.ServiceIndexResponse + DecodeJSON(t, resp, &result) + + assert.Equal(t, "3.0.0", result.Version) + assert.NotEmpty(t, result.Resources) + + root := setting.AppURL + url[1:] + for _, r := range result.Resources { + switch r.Type { + case "SearchQueryService": + fallthrough + case "SearchQueryService/3.0.0-beta": + fallthrough + case "SearchQueryService/3.0.0-rc": + assert.Equal(t, root+"/query", r.ID) + case "RegistrationsBaseUrl": + fallthrough + case "RegistrationsBaseUrl/3.0.0-beta": + fallthrough + case "RegistrationsBaseUrl/3.0.0-rc": + assert.Equal(t, root+"/registration", r.ID) + case "PackageBaseAddress/3.0.0": + assert.Equal(t, root+"/package", r.ID) + case "PackagePublish/2.0.0": + assert.Equal(t, root, r.ID) + } + } + }) + + t.Run("Upload", func(t *testing.T) { + t.Run("DependencyPackage", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusCreated) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNuGet) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.IsType(t, &nuget_module.Metadata{}, pd.Metadata) + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + assert.Equal(t, fmt.Sprintf("%s.%s.nupkg", packageName, packageVersion), pfs[0].Name) + assert.True(t, pfs[0].IsLead) + + pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(len(content)), pb.Size) + + req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusConflict) + }) + + t.Run("SymbolPackage", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + createPackage := func(id, packageType string) io.Reader { + var buf bytes.Buffer + archive := zip.NewWriter(&buf) + + w, _ := archive.Create("package.nuspec") + w.Write([]byte(`<?xml version="1.0" encoding="utf-8"?> + <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> + <metadata> + <id>` + id + `</id> + <version>` + packageVersion + `</version> + <authors>` + packageAuthors + `</authors> + <description>` + packageDescription + `</description> + <packageTypes><packageType name="` + packageType + `" /></packageTypes> + </metadata> + </package>`)) + + w, _ = archive.Create(symbolFilename) + b, _ := base64.StdEncoding.DecodeString(`QlNKQgEAAQAAAAAADAAAAFBEQiB2MS4wAAAAAAAABgB8AAAAWAAAACNQZGIAAAAA1AAAAAgBAAAj +fgAA3AEAAAQAAAAjU3RyaW5ncwAAAADgAQAABAAAACNVUwDkAQAAMAAAACNHVUlEAAAAFAIAACgB +AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) + w.Write(b) + + archive.Close() + return &buf + } + + req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage("unknown-package", "SymbolsPackage")) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage(packageName, "DummyPackage")) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusBadRequest) + + req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage(packageName, "SymbolsPackage")) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusCreated) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNuGet) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.IsType(t, &nuget_module.Metadata{}, pd.Metadata) + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 3) + for _, pf := range pfs { + switch pf.Name { + case fmt.Sprintf("%s.%s.nupkg", packageName, packageVersion): + case fmt.Sprintf("%s.%s.snupkg", packageName, packageVersion): + assert.False(t, pf.IsLead) + + pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(616), pb.Size) + case symbolFilename: + assert.False(t, pf.IsLead) + + pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(160), pb.Size) + + pps, err := packages.GetProperties(db.DefaultContext, packages.PropertyTypeFile, pf.ID) + assert.NoError(t, err) + assert.Len(t, pps, 1) + assert.Equal(t, nuget_module.PropertySymbolID, pps[0].Name) + assert.Equal(t, symbolID, pps[0].Value) + default: + assert.Fail(t, "unexpected file: %v", pf.Name) + } + } + + req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage(packageName, "SymbolsPackage")) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusConflict) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + checkDownloadCount := func(count int64) { + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNuGet) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + assert.Equal(t, count, pvs[0].DownloadCount) + } + + checkDownloadCount(0) + + req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.nupkg", url, packageName, packageVersion, packageName, packageVersion)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, content, resp.Body.Bytes()) + + checkDownloadCount(1) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.snupkg", url, packageName, packageVersion, packageName, packageVersion)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusOK) + + checkDownloadCount(1) + + t.Run("Symbol", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/symbols/%s/%sFFFFFFFF/gitea.pdb", url, symbolFilename, symbolID)) + MakeRequest(t, req, http.StatusBadRequest) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/symbols/%s/%sFFFFFFFF/%s", url, symbolFilename, "00000000000000000000000000000000", symbolFilename)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/symbols/%s/%sFFFFFFFF/%s", url, symbolFilename, symbolID, symbolFilename)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusOK) + + checkDownloadCount(1) + }) + }) + + t.Run("SearchService", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + cases := []struct { + Query string + Skip int + Take int + ExpectedTotal int64 + ExpectedResults int + }{ + {"", 0, 0, 1, 1}, + {"", 0, 10, 1, 1}, + {"gitea", 0, 10, 0, 0}, + {"test", 0, 10, 1, 1}, + {"test", 1, 10, 1, 0}, + } + + for i, c := range cases { + req := NewRequest(t, "GET", fmt.Sprintf("%s/query?q=%s&skip=%d&take=%d", url, c.Query, c.Skip, c.Take)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var result nuget.SearchResultResponse + DecodeJSON(t, resp, &result) + + assert.Equal(t, c.ExpectedTotal, result.TotalHits, "case %d: unexpected total hits", i) + assert.Len(t, result.Data, c.ExpectedResults, "case %d: unexpected result count", i) + } + }) + + t.Run("RegistrationService", func(t *testing.T) { + indexURL := fmt.Sprintf("%s%s/registration/%s/index.json", setting.AppURL, url[1:], packageName) + leafURL := fmt.Sprintf("%s%s/registration/%s/%s.json", setting.AppURL, url[1:], packageName, packageVersion) + contentURL := fmt.Sprintf("%s%s/package/%s/%s/%s.%s.nupkg", setting.AppURL, url[1:], packageName, packageVersion, packageName, packageVersion) + + t.Run("RegistrationIndex", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/registration/%s/index.json", url, packageName)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var result nuget.RegistrationIndexResponse + DecodeJSON(t, resp, &result) + + assert.Equal(t, indexURL, result.RegistrationIndexURL) + assert.Equal(t, 1, result.Count) + assert.Len(t, result.Pages, 1) + assert.Equal(t, indexURL, result.Pages[0].RegistrationPageURL) + assert.Equal(t, packageVersion, result.Pages[0].Lower) + assert.Equal(t, packageVersion, result.Pages[0].Upper) + assert.Equal(t, 1, result.Pages[0].Count) + assert.Len(t, result.Pages[0].Items, 1) + assert.Equal(t, packageName, result.Pages[0].Items[0].CatalogEntry.ID) + assert.Equal(t, packageVersion, result.Pages[0].Items[0].CatalogEntry.Version) + assert.Equal(t, packageAuthors, result.Pages[0].Items[0].CatalogEntry.Authors) + assert.Equal(t, packageDescription, result.Pages[0].Items[0].CatalogEntry.Description) + assert.Equal(t, leafURL, result.Pages[0].Items[0].CatalogEntry.CatalogLeafURL) + assert.Equal(t, contentURL, result.Pages[0].Items[0].CatalogEntry.PackageContentURL) + }) + + t.Run("RegistrationLeaf", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/registration/%s/%s.json", url, packageName, packageVersion)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var result nuget.RegistrationLeafResponse + DecodeJSON(t, resp, &result) + + assert.Equal(t, leafURL, result.RegistrationLeafURL) + assert.Equal(t, contentURL, result.PackageContentURL) + assert.Equal(t, indexURL, result.RegistrationIndexURL) + }) + }) + + t.Run("PackageService", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/index.json", url, packageName)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var result nuget.PackageVersionsResponse + DecodeJSON(t, resp, &result) + + assert.Len(t, result.Versions, 1) + assert.Equal(t, packageVersion, result.Versions[0]) + }) + + t.Run("Delete", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, packageVersion)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNoContent) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNuGet) + assert.NoError(t, err) + assert.Empty(t, pvs) + }) + + t.Run("DownloadNotExists", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.nupkg", url, packageName, packageVersion, packageName, packageVersion)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.snupkg", url, packageName, packageVersion, packageName, packageVersion)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("DeleteNotExists", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s", url, packageName, packageVersion)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNotFound) + }) +} diff --git a/tests/integration/api_packages_pub_test.go b/tests/integration/api_packages_pub_test.go new file mode 100644 index 0000000000..9e4ce63fa1 --- /dev/null +++ b/tests/integration/api_packages_pub_test.go @@ -0,0 +1,180 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/http/httptest" + "testing" + "time" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + pub_module "code.gitea.io/gitea/modules/packages/pub" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestPackagePub(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + token := "Bearer " + getUserToken(t, user.Name) + + packageName := "test_package" + packageVersion := "1.0.1" + packageDescription := "Test Description" + + filename := fmt.Sprintf("%s.tar.gz", packageVersion) + + pubspecContent := `name: ` + packageName + ` +version: ` + packageVersion + ` +description: ` + packageDescription + + var buf bytes.Buffer + zw := gzip.NewWriter(&buf) + archive := tar.NewWriter(zw) + archive.WriteHeader(&tar.Header{ + Name: "pubspec.yaml", + Mode: 0o600, + Size: int64(len(pubspecContent)), + }) + archive.Write([]byte(pubspecContent)) + archive.Close() + zw.Close() + content := buf.Bytes() + + root := fmt.Sprintf("/api/packages/%s/pub", user.Name) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + uploadURL := root + "/api/packages/versions/new" + + req := NewRequest(t, "GET", uploadURL) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "GET", uploadURL) + addTokenAuthHeader(req, token) + resp := MakeRequest(t, req, http.StatusOK) + + type UploadRequest struct { + URL string `json:"url"` + Fields map[string]string `json:"fields"` + } + + var result UploadRequest + DecodeJSON(t, resp, &result) + + assert.Empty(t, result.Fields) + + uploadFile := func(t *testing.T, url string, content []byte, expectedStatus int) *httptest.ResponseRecorder { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, _ := writer.CreateFormFile("file", "dummy.tar.gz") + _, _ = io.Copy(part, bytes.NewReader(content)) + + _ = writer.Close() + + req := NewRequestWithBody(t, "POST", url, body) + req.Header.Add("Content-Type", writer.FormDataContentType()) + addTokenAuthHeader(req, token) + return MakeRequest(t, req, expectedStatus) + } + + resp = uploadFile(t, result.URL, content, http.StatusNoContent) + + req = NewRequest(t, "GET", resp.Header().Get("Location")) + addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypePub) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.IsType(t, &pub_module.Metadata{}, pd.Metadata) + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + assert.Equal(t, filename, pfs[0].Name) + assert.True(t, pfs[0].IsLead) + + pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(len(content)), pb.Size) + + resp = uploadFile(t, result.URL, content, http.StatusBadRequest) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/api/packages/%s/%s", root, packageName, packageVersion)) + resp := MakeRequest(t, req, http.StatusOK) + + type VersionMetadata struct { + Version string `json:"version"` + ArchiveURL string `json:"archive_url"` + Published time.Time `json:"published"` + Pubspec interface{} `json:"pubspec,omitempty"` + } + + var result VersionMetadata + DecodeJSON(t, resp, &result) + + assert.Equal(t, packageVersion, result.Version) + assert.NotNil(t, result.Pubspec) + + req = NewRequest(t, "GET", result.ArchiveURL) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, content, resp.Body.Bytes()) + }) + + t.Run("EnumeratePackageVersions", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/api/packages/%s", root, packageName)) + resp := MakeRequest(t, req, http.StatusOK) + + type VersionMetadata struct { + Version string `json:"version"` + ArchiveURL string `json:"archive_url"` + Published time.Time `json:"published"` + Pubspec interface{} `json:"pubspec,omitempty"` + } + + type PackageVersions struct { + Name string `json:"name"` + Latest *VersionMetadata `json:"latest"` + Versions []*VersionMetadata `json:"versions"` + } + + var result PackageVersions + DecodeJSON(t, resp, &result) + + assert.Equal(t, packageName, result.Name) + assert.NotNil(t, result.Latest) + assert.Len(t, result.Versions, 1) + assert.Equal(t, result.Latest.Version, result.Versions[0].Version) + assert.Equal(t, packageVersion, result.Latest.Version) + assert.NotNil(t, result.Latest.Pubspec) + }) +} diff --git a/tests/integration/api_packages_pypi_test.go b/tests/integration/api_packages_pypi_test.go new file mode 100644 index 0000000000..32b3304ca7 --- /dev/null +++ b/tests/integration/api_packages_pypi_test.go @@ -0,0 +1,182 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "bytes" + "fmt" + "io" + "mime/multipart" + "net/http" + "regexp" + "strings" + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/packages/pypi" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestPackagePyPI(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + packageName := "test-package" + packageVersion := "1.0.1" + packageAuthor := "KN4CK3R" + packageDescription := "Test Description" + + content := "test" + hashSHA256 := "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" + + root := fmt.Sprintf("/api/packages/%s/pypi", user.Name) + + uploadFile := func(t *testing.T, filename, content string, expectedStatus int) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, _ := writer.CreateFormFile("content", filename) + _, _ = io.Copy(part, strings.NewReader(content)) + + writer.WriteField("name", packageName) + writer.WriteField("version", packageVersion) + writer.WriteField("author", packageAuthor) + writer.WriteField("summary", packageDescription) + writer.WriteField("description", packageDescription) + writer.WriteField("sha256_digest", hashSHA256) + writer.WriteField("requires_python", "3.6") + + _ = writer.Close() + + req := NewRequestWithBody(t, "POST", root, body) + req.Header.Add("Content-Type", writer.FormDataContentType()) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, expectedStatus) + } + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + filename := "test.whl" + uploadFile(t, filename, content, http.StatusCreated) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypePyPI) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.IsType(t, &pypi.Metadata{}, pd.Metadata) + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + assert.Equal(t, filename, pfs[0].Name) + assert.True(t, pfs[0].IsLead) + + pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(4), pb.Size) + }) + + t.Run("UploadAddFile", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + filename := "test.tar.gz" + uploadFile(t, filename, content, http.StatusCreated) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypePyPI) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.IsType(t, &pypi.Metadata{}, pd.Metadata) + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 2) + + pf, err := packages.GetFileForVersionByName(db.DefaultContext, pvs[0].ID, filename, packages.EmptyFileKey) + assert.NoError(t, err) + assert.Equal(t, filename, pf.Name) + assert.True(t, pf.IsLead) + + pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(4), pb.Size) + }) + + t.Run("UploadHashMismatch", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + filename := "test2.whl" + uploadFile(t, filename, "dummy", http.StatusBadRequest) + }) + + t.Run("UploadExists", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + uploadFile(t, "test.whl", content, http.StatusBadRequest) + uploadFile(t, "test.tar.gz", content, http.StatusBadRequest) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + downloadFile := func(filename string) { + req := NewRequest(t, "GET", fmt.Sprintf("%s/files/%s/%s/%s", root, packageName, packageVersion, filename)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, []byte(content), resp.Body.Bytes()) + } + + downloadFile("test.whl") + downloadFile("test.tar.gz") + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypePyPI) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + assert.Equal(t, int64(2), pvs[0].DownloadCount) + }) + + t.Run("PackageMetadata", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/simple/%s", root, packageName)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + nodes := htmlDoc.doc.Find("a").Nodes + assert.Len(t, nodes, 2) + + hrefMatcher := regexp.MustCompile(fmt.Sprintf(`%s/files/%s/%s/test\..+#sha256-%s`, root, packageName, packageVersion, hashSHA256)) + + for _, a := range nodes { + for _, att := range a.Attr { + switch att.Key { + case "href": + assert.Regexp(t, hrefMatcher, att.Val) + case "data-requires-python": + assert.Equal(t, "3.6", att.Val) + default: + t.Fail() + } + } + } + }) +} diff --git a/tests/integration/api_packages_rubygems_test.go b/tests/integration/api_packages_rubygems_test.go new file mode 100644 index 0000000000..6cf5af710b --- /dev/null +++ b/tests/integration/api_packages_rubygems_test.go @@ -0,0 +1,227 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "bytes" + "encoding/base64" + "fmt" + "mime/multipart" + "net/http" + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/packages/rubygems" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestPackageRubyGems(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + packageName := "gitea" + packageVersion := "1.0.5" + packageFilename := "gitea-1.0.5.gem" + + gemContent, _ := base64.StdEncoding.DecodeString(`bWV0YWRhdGEuZ3oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA0NDQAMDAwMDAw +MAAwMDAwMDAwADAwMDAwMDAxMDQxADE0MTEwNzcyMzY2ADAxMzQ0MQAgMAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMHdoZWVsAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAd2hlZWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAw +MDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf +iwgA9vQjYQID1VVNb9QwEL37V5he9pRsmlJAFlQckCoOXAriQIUix5nNmsYf2JOqKwS/nYmz2d3Q +qqCCKpFdadfjmfdm5nmcLMv4k9DXm6Wrv4BCcQ5GiPcelF5pJVE7y6w0IHirESS7hhDJJu4I+jhu +Mc53Tsd5kZ8y30lcuWAEH2KY7HHtQhQs4+cJkwwuwNdeB6JhtbaNDoLTL1MQsFJrqQnr8jNrJJJH +WZTHWfEiK094UYj0zYvp4Z9YAx5sA1ZpSCS3M30zeWwo2bG60FvUBjIKJts2GwMW76r0Yr9NzjN3 +YhwsGX2Ozl4dpcWwvK9d43PQtDIv9igvHwSyIIwFmXHjqTqxLY8MPkCADmQk80p2EfZ6VbM6/ue6 +/1D0Bq7/qeA/zh6W82leHmhFWUHn/JbsEfT6q7QbiCpoj8l0QcEUFLmX6kq2wBEiMjBSd+Pwt7T5 +Ot0kuXYMbkD1KOuOBnWYb7hBsAP4bhlkFRqnqpWefMZ/pHCn6+WIFGq2dgY8EQq+RvRRLJcTyZJ1 +WhHqGPTu7QdmACXdJFLwb9+ZdxErbSPKrqsMxJhAWCJ1qaqRdtu6yktcT/STsamG0qp7rsa5EL/K +MBua30uw4ynzExqYWRJDfx8/kQWN3PwsDh2jYLr1W+pZcAmCs9splvnz/Flesqhbq21bXcGG/OLh ++2fv/JTF3hgZyCW9OaZjxoZjdnBGfgKpxZyJ1QYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGF0 +YS50YXIuZ3oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA0NDQAMDAwMDAwMAAw +MDAwMDAwADAwMDAwMDAwMjQyADE0MTEwNzcyMzY2ADAxMzM2MQAgMAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMHdoZWVsAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAd2hlZWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAwMDAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfiwgA +9vQjYQID7M/NCsMgDABgz32KrA/QxersK/Q17ExXIcyhlr7+HLv1sJ02KPhBCPk5JOyn881nsl2c +xI+gRDRaC3zbZ8RBCamlxGHolTFlX11kLwDFH6wp21hO2RYi/rD3bb5/7iCubFOCMbBtABzNkIjn +bvGlAnisOUE7EnOALUR2p7b06e6aV4iqqqrquJ4AAAD//wMA+sA/NQAIAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGNoZWNr +c3Vtcy55YW1sLmd6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwNDQ0ADAwMDAwMDAAMDAw +MDAwMAAwMDAwMDAwMDQ1MAAxNDExMDc3MjM2NgAwMTQ2MTIAIDAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdXN0YXIAMDB3aGVlbAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAHdoZWVsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDAwMAAwMDAwMDAwAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4sIAPb0 +I2ECA2WQOa4UQAxE8znFXGCQ21vbPyMj5wRuL0Qk6EecnmZCyKyy9FSvXq/X4/u3ryj68Xg+f/Zn +VHzGlx+/P57qvU4XxWalBKftSXOgCjNYkdRycrC5Axem+W4HqS12PNEv7836jF9vnlHxwSyxKY+y +go0cPblyHzkrZ4HF1GSVhe7mOOoasXNk2fnbUxb+19Pp9tobD/QlJKMX7y204PREh6nQ5hG9Alw6 +x4TnmtA+aekGfm6wAseog2LSgpR4Q7cYnAH3K4qAQa6A6JCC1gpuY7P+9YxE5SZ+j0eVGbaBTwBQ +iIqRUyyzLCoFCBdYNWxniapTavD97blXTzFvgoVoAsKBAtlU48cdaOmeZDpwV01OtcGwjscfeUrY +B9QBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`) + + root := fmt.Sprintf("/api/packages/%s/rubygems", user.Name) + + uploadFile := func(t *testing.T, expectedStatus int) { + req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/api/v1/gems", root), bytes.NewReader(gemContent)) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, expectedStatus) + } + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + uploadFile(t, http.StatusCreated) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.IsType(t, &rubygems.Metadata{}, pd.Metadata) + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + assert.Equal(t, packageFilename, pfs[0].Name) + assert.True(t, pfs[0].IsLead) + + pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(4608), pb.Size) + }) + + t.Run("UploadExists", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + uploadFile(t, http.StatusBadRequest) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/gems/%s", root, packageFilename)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, gemContent, resp.Body.Bytes()) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + assert.Equal(t, int64(1), pvs[0].DownloadCount) + }) + + t.Run("DownloadGemspec", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/quick/Marshal.4.8/%sspec.rz", root, packageFilename)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + b, _ := base64.StdEncoding.DecodeString(`eJxi4Si1EndPzbWyCi5ITc5My0xOLMnMz2M8zMIRLeGpxGWsZ6RnzGbF5hqSyempxJWeWZKayGbN +EBJqJQjWFZZaVJyZnxfN5qnEZahnoGcKkjTwVBJyB6lUKEhMzk5MTwULGngqcRaVJlWCONEMBp5K +DGAWSKc7zFhPJamg0qRK99TcYphehZLU4hKInFhGSUlBsZW+PtgZepn5+iDxECRzDUDGcfh6hoA4 +gAAAAP//MS06Gw==`) + assert.Equal(t, b, resp.Body.Bytes()) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + assert.Equal(t, int64(1), pvs[0].DownloadCount) + }) + + t.Run("EnumeratePackages", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + enumeratePackages := func(t *testing.T, endpoint string, expectedContent []byte) { + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s", root, endpoint)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, expectedContent, resp.Body.Bytes()) + } + + b, _ := base64.StdEncoding.DecodeString(`H4sICAAAAAAA/3NwZWNzLjQuOABi4Yhmi+bwVOJKzyxJTWSzYnMNCbUSdE/NtbIKSy0qzszPi2bzVOIy1DPQM2WzZgjxVOIsKk2qBDEBAQAA///xOEYKOwAAAA==`) + enumeratePackages(t, "specs.4.8.gz", b) + b, _ = base64.StdEncoding.DecodeString(`H4sICAAAAAAA/2xhdGVzdF9zcGVjcy40LjgAYuGIZovm8FTiSs8sSU1ks2JzDQm1EnRPzbWyCkstKs7Mz4tm81TiMtQz0DNls2YI8VTiLCpNqgQxAQEAAP//8ThGCjsAAAA=`) + enumeratePackages(t, "latest_specs.4.8.gz", b) + b, _ = base64.StdEncoding.DecodeString(`H4sICAAAAAAA/3ByZXJlbGVhc2Vfc3BlY3MuNC44AGLhiGYABAAA//9snXr5BAAAAA==`) + enumeratePackages(t, "prerelease_specs.4.8.gz", b) + }) + + t.Run("Delete", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + body := bytes.Buffer{} + writer := multipart.NewWriter(&body) + writer.WriteField("gem_name", packageName) + writer.WriteField("version", packageVersion) + writer.Close() + + req := NewRequestWithBody(t, "DELETE", fmt.Sprintf("%s/api/v1/gems/yank", root), &body) + req.Header.Add("Content-Type", writer.FormDataContentType()) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusOK) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems) + assert.NoError(t, err) + assert.Empty(t, pvs) + }) +} diff --git a/tests/integration/api_packages_test.go b/tests/integration/api_packages_test.go new file mode 100644 index 0000000000..86d81994d4 --- /dev/null +++ b/tests/integration/api_packages_test.go @@ -0,0 +1,168 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "bytes" + "fmt" + "net/http" + "testing" + "time" + + "code.gitea.io/gitea/models/db" + packages_model "code.gitea.io/gitea/models/packages" + container_model "code.gitea.io/gitea/models/packages/container" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + packages_service "code.gitea.io/gitea/services/packages" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestPackageAPI(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + packageName := "test-package" + packageVersion := "1.0.3" + filename := "file.bin" + + url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, packageVersion, filename) + req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{})) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusCreated) + + t.Run("ListPackages", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s?token=%s", user.Name, token)) + resp := MakeRequest(t, req, http.StatusOK) + + var apiPackages []*api.Package + DecodeJSON(t, resp, &apiPackages) + + assert.Len(t, apiPackages, 1) + assert.Equal(t, string(packages_model.TypeGeneric), apiPackages[0].Type) + assert.Equal(t, packageName, apiPackages[0].Name) + assert.Equal(t, packageVersion, apiPackages[0].Version) + assert.NotNil(t, apiPackages[0].Creator) + assert.Equal(t, user.Name, apiPackages[0].Creator.UserName) + }) + + t.Run("GetPackage", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/dummy/%s/%s?token=%s", user.Name, packageName, packageVersion, token)) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s?token=%s", user.Name, packageName, packageVersion, token)) + resp := MakeRequest(t, req, http.StatusOK) + + var p *api.Package + DecodeJSON(t, resp, &p) + + assert.Equal(t, string(packages_model.TypeGeneric), p.Type) + assert.Equal(t, packageName, p.Name) + assert.Equal(t, packageVersion, p.Version) + assert.NotNil(t, p.Creator) + assert.Equal(t, user.Name, p.Creator.UserName) + + t.Run("RepositoryLink", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + p, err := packages_model.GetPackageByName(db.DefaultContext, user.ID, packages_model.TypeGeneric, packageName) + assert.NoError(t, err) + + // no repository link + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s?token=%s", user.Name, packageName, packageVersion, token)) + resp := MakeRequest(t, req, http.StatusOK) + + var ap1 *api.Package + DecodeJSON(t, resp, &ap1) + assert.Nil(t, ap1.Repository) + + // link to public repository + assert.NoError(t, packages_model.SetRepositoryLink(db.DefaultContext, p.ID, 1)) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s?token=%s", user.Name, packageName, packageVersion, token)) + resp = MakeRequest(t, req, http.StatusOK) + + var ap2 *api.Package + DecodeJSON(t, resp, &ap2) + assert.NotNil(t, ap2.Repository) + assert.EqualValues(t, 1, ap2.Repository.ID) + + // link to private repository + assert.NoError(t, packages_model.SetRepositoryLink(db.DefaultContext, p.ID, 2)) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s?token=%s", user.Name, packageName, packageVersion, token)) + resp = MakeRequest(t, req, http.StatusOK) + + var ap3 *api.Package + DecodeJSON(t, resp, &ap3) + assert.Nil(t, ap3.Repository) + + assert.NoError(t, packages_model.UnlinkRepositoryFromAllPackages(db.DefaultContext, 2)) + }) + }) + + t.Run("ListPackageFiles", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/dummy/%s/%s/files?token=%s", user.Name, packageName, packageVersion, token)) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s/files?token=%s", user.Name, packageName, packageVersion, token)) + resp := MakeRequest(t, req, http.StatusOK) + + var files []*api.PackageFile + DecodeJSON(t, resp, &files) + + assert.Len(t, files, 1) + assert.Equal(t, int64(0), files[0].Size) + assert.Equal(t, filename, files[0].Name) + assert.Equal(t, "d41d8cd98f00b204e9800998ecf8427e", files[0].HashMD5) + assert.Equal(t, "da39a3ee5e6b4b0d3255bfef95601890afd80709", files[0].HashSHA1) + assert.Equal(t, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", files[0].HashSHA256) + assert.Equal(t, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", files[0].HashSHA512) + }) + + t.Run("DeletePackage", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/dummy/%s/%s?token=%s", user.Name, packageName, packageVersion, token)) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s?token=%s", user.Name, packageName, packageVersion, token)) + MakeRequest(t, req, http.StatusNoContent) + }) +} + +func TestPackageCleanup(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + time.Sleep(time.Second) + + pbs, err := packages_model.FindExpiredUnreferencedBlobs(db.DefaultContext, time.Duration(0)) + assert.NoError(t, err) + assert.NotEmpty(t, pbs) + + _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, 2, packages_model.TypeContainer, "test", container_model.UploadVersion) + assert.NoError(t, err) + + err = packages_service.Cleanup(nil, time.Duration(0)) + assert.NoError(t, err) + + pbs, err = packages_model.FindExpiredUnreferencedBlobs(db.DefaultContext, time.Duration(0)) + assert.NoError(t, err) + assert.Empty(t, pbs) + + _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, 2, packages_model.TypeContainer, "test", container_model.UploadVersion) + assert.ErrorIs(t, err, packages_model.ErrPackageNotExist) +} diff --git a/tests/integration/api_packages_vagrant_test.go b/tests/integration/api_packages_vagrant_test.go new file mode 100644 index 0000000000..1d2952e1a2 --- /dev/null +++ b/tests/integration/api_packages_vagrant_test.go @@ -0,0 +1,171 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "fmt" + "net/http" + "strings" + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/json" + vagrant_module "code.gitea.io/gitea/modules/packages/vagrant" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestPackageVagrant(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + token := "Bearer " + getUserToken(t, user.Name) + + packageName := "test_package" + packageVersion := "1.0.1" + packageDescription := "Test Description" + packageProvider := "virtualbox" + + filename := fmt.Sprintf("%s.box", packageProvider) + + infoContent, _ := json.Marshal(map[string]string{ + "description": packageDescription, + }) + + var buf bytes.Buffer + zw := gzip.NewWriter(&buf) + archive := tar.NewWriter(zw) + archive.WriteHeader(&tar.Header{ + Name: "info.json", + Mode: 0o600, + Size: int64(len(infoContent)), + }) + archive.Write(infoContent) + archive.Close() + zw.Close() + content := buf.Bytes() + + root := fmt.Sprintf("/api/packages/%s/vagrant", user.Name) + + t.Run("Authenticate", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + authenticateURL := fmt.Sprintf("%s/authenticate", root) + + req := NewRequest(t, "GET", authenticateURL) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "GET", authenticateURL) + addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusOK) + }) + + boxURL := fmt.Sprintf("%s/%s", root, packageName) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "HEAD", boxURL) + MakeRequest(t, req, http.StatusNotFound) + + uploadURL := fmt.Sprintf("%s/%s/%s", boxURL, packageVersion, filename) + + req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(content)) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(content)) + addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "HEAD", boxURL) + resp := MakeRequest(t, req, http.StatusOK) + assert.True(t, strings.HasPrefix(resp.HeaderMap.Get("Content-Type"), "application/json")) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeVagrant) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.IsType(t, &vagrant_module.Metadata{}, pd.Metadata) + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + assert.Equal(t, filename, pfs[0].Name) + assert.True(t, pfs[0].IsLead) + + pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(len(content)), pb.Size) + + req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(content)) + addTokenAuthHeader(req, token) + MakeRequest(t, req, http.StatusConflict) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s", boxURL, packageVersion, filename)) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, content, resp.Body.Bytes()) + }) + + t.Run("EnumeratePackageVersions", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", boxURL) + resp := MakeRequest(t, req, http.StatusOK) + + type providerData struct { + Name string `json:"name"` + URL string `json:"url"` + Checksum string `json:"checksum"` + ChecksumType string `json:"checksum_type"` + } + + type versionMetadata struct { + Version string `json:"version"` + Status string `json:"status"` + DescriptionHTML string `json:"description_html,omitempty"` + DescriptionMarkdown string `json:"description_markdown,omitempty"` + Providers []*providerData `json:"providers"` + } + + type packageMetadata struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + ShortDescription string `json:"short_description,omitempty"` + Versions []*versionMetadata `json:"versions"` + } + + var result packageMetadata + DecodeJSON(t, resp, &result) + + assert.Equal(t, packageName, result.Name) + assert.Equal(t, packageDescription, result.Description) + assert.Len(t, result.Versions, 1) + version := result.Versions[0] + assert.Equal(t, packageVersion, version.Version) + assert.Equal(t, "active", version.Status) + assert.Len(t, version.Providers, 1) + provider := version.Providers[0] + assert.Equal(t, packageProvider, provider.Name) + assert.Equal(t, "sha512", provider.ChecksumType) + assert.Equal(t, "259bebd6160acad695016d22a45812e26f187aaf78e71a4c23ee3201528346293f991af3468a8c6c5d2a21d7d9e1bdc1bf79b87110b2fddfcc5a0d45963c7c30", provider.Checksum) + }) +} diff --git a/tests/integration/api_private_serv_test.go b/tests/integration/api_private_serv_test.go new file mode 100644 index 0000000000..6fd6d616db --- /dev/null +++ b/tests/integration/api_private_serv_test.go @@ -0,0 +1,154 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "context" + "net/url" + "testing" + + asymkey_model "code.gitea.io/gitea/models/asymkey" + "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/modules/private" + + "github.com/stretchr/testify/assert" +) + +func TestAPIPrivateNoServ(t *testing.T) { + onGiteaRun(t, func(*testing.T, *url.URL) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + key, user, err := private.ServNoCommand(ctx, 1) + assert.NoError(t, err) + assert.Equal(t, int64(2), user.ID) + assert.Equal(t, "user2", user.Name) + assert.Equal(t, int64(1), key.ID) + assert.Equal(t, "user2@localhost", key.Name) + + deployKey, err := asymkey_model.AddDeployKey(1, "test-deploy", "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment", false) + assert.NoError(t, err) + + key, user, err = private.ServNoCommand(ctx, deployKey.KeyID) + assert.NoError(t, err) + assert.Empty(t, user) + assert.Equal(t, deployKey.KeyID, key.ID) + assert.Equal(t, "test-deploy", key.Name) + }) +} + +func TestAPIPrivateServ(t *testing.T) { + onGiteaRun(t, func(*testing.T, *url.URL) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Can push to a repo we own + results, err := private.ServCommand(ctx, 1, "user2", "repo1", perm.AccessModeWrite, "git-upload-pack", "") + assert.NoError(t, err) + assert.False(t, results.IsWiki) + assert.Zero(t, results.DeployKeyID) + assert.Equal(t, int64(1), results.KeyID) + assert.Equal(t, "user2@localhost", results.KeyName) + assert.Equal(t, "user2", results.UserName) + assert.Equal(t, int64(2), results.UserID) + assert.Equal(t, "user2", results.OwnerName) + assert.Equal(t, "repo1", results.RepoName) + assert.Equal(t, int64(1), results.RepoID) + + // Cannot push to a private repo we're not associated with + results, err = private.ServCommand(ctx, 1, "user15", "big_test_private_1", perm.AccessModeWrite, "git-upload-pack", "") + assert.Error(t, err) + assert.Empty(t, results) + + // Cannot pull from a private repo we're not associated with + results, err = private.ServCommand(ctx, 1, "user15", "big_test_private_1", perm.AccessModeRead, "git-upload-pack", "") + assert.Error(t, err) + assert.Empty(t, results) + + // Can pull from a public repo we're not associated with + results, err = private.ServCommand(ctx, 1, "user15", "big_test_public_1", perm.AccessModeRead, "git-upload-pack", "") + assert.NoError(t, err) + assert.False(t, results.IsWiki) + assert.Zero(t, results.DeployKeyID) + assert.Equal(t, int64(1), results.KeyID) + assert.Equal(t, "user2@localhost", results.KeyName) + assert.Equal(t, "user2", results.UserName) + assert.Equal(t, int64(2), results.UserID) + assert.Equal(t, "user15", results.OwnerName) + assert.Equal(t, "big_test_public_1", results.RepoName) + assert.Equal(t, int64(17), results.RepoID) + + // Cannot push to a public repo we're not associated with + results, err = private.ServCommand(ctx, 1, "user15", "big_test_public_1", perm.AccessModeWrite, "git-upload-pack", "") + assert.Error(t, err) + assert.Empty(t, results) + + // Add reading deploy key + deployKey, err := asymkey_model.AddDeployKey(19, "test-deploy", "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment", true) + assert.NoError(t, err) + + // Can pull from repo we're a deploy key for + results, err = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_1", perm.AccessModeRead, "git-upload-pack", "") + assert.NoError(t, err) + assert.False(t, results.IsWiki) + assert.NotZero(t, results.DeployKeyID) + assert.Equal(t, deployKey.KeyID, results.KeyID) + assert.Equal(t, "test-deploy", results.KeyName) + assert.Equal(t, "user15", results.UserName) + assert.Equal(t, int64(15), results.UserID) + assert.Equal(t, "user15", results.OwnerName) + assert.Equal(t, "big_test_private_1", results.RepoName) + assert.Equal(t, int64(19), results.RepoID) + + // Cannot push to a private repo with reading key + results, err = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_1", perm.AccessModeWrite, "git-upload-pack", "") + assert.Error(t, err) + assert.Empty(t, results) + + // Cannot pull from a private repo we're not associated with + results, err = private.ServCommand(ctx, deployKey.ID, "user15", "big_test_private_2", perm.AccessModeRead, "git-upload-pack", "") + assert.Error(t, err) + assert.Empty(t, results) + + // Cannot pull from a public repo we're not associated with + results, err = private.ServCommand(ctx, deployKey.ID, "user15", "big_test_public_1", perm.AccessModeRead, "git-upload-pack", "") + assert.Error(t, err) + assert.Empty(t, results) + + // Add writing deploy key + deployKey, err = asymkey_model.AddDeployKey(20, "test-deploy", "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment", false) + assert.NoError(t, err) + + // Cannot push to a private repo with reading key + results, err = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_1", perm.AccessModeWrite, "git-upload-pack", "") + assert.Error(t, err) + assert.Empty(t, results) + + // Can pull from repo we're a writing deploy key for + results, err = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_2", perm.AccessModeRead, "git-upload-pack", "") + assert.NoError(t, err) + assert.False(t, results.IsWiki) + assert.NotZero(t, results.DeployKeyID) + assert.Equal(t, deployKey.KeyID, results.KeyID) + assert.Equal(t, "test-deploy", results.KeyName) + assert.Equal(t, "user15", results.UserName) + assert.Equal(t, int64(15), results.UserID) + assert.Equal(t, "user15", results.OwnerName) + assert.Equal(t, "big_test_private_2", results.RepoName) + assert.Equal(t, int64(20), results.RepoID) + + // Can push to repo we're a writing deploy key for + results, err = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_2", perm.AccessModeWrite, "git-upload-pack", "") + assert.NoError(t, err) + assert.False(t, results.IsWiki) + assert.NotZero(t, results.DeployKeyID) + assert.Equal(t, deployKey.KeyID, results.KeyID) + assert.Equal(t, "test-deploy", results.KeyName) + assert.Equal(t, "user15", results.UserName) + assert.Equal(t, int64(15), results.UserID) + assert.Equal(t, "user15", results.OwnerName) + assert.Equal(t, "big_test_private_2", results.RepoName) + assert.Equal(t, int64(20), results.RepoID) + }) +} diff --git a/tests/integration/api_pull_commits_test.go b/tests/integration/api_pull_commits_test.go new file mode 100644 index 0000000000..aa58f44bbe --- /dev/null +++ b/tests/integration/api_pull_commits_test.go @@ -0,0 +1,41 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIPullCommits(t *testing.T) { + defer tests.PrepareTestEnv(t)() + pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) + assert.NoError(t, pullIssue.LoadIssue()) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue.HeadRepoID}) + + session := loginUser(t, "user2") + req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/commits", repo.OwnerName, repo.Name, pullIssue.Index) + resp := session.MakeRequest(t, req, http.StatusOK) + + var commits []*api.Commit + DecodeJSON(t, resp, &commits) + + if !assert.Len(t, commits, 2) { + return + } + + assert.Equal(t, "5f22f7d0d95d614d25a5b68592adb345a4b5c7fd", commits[0].SHA) + assert.Equal(t, "4a357436d925b5c974181ff12a994538ddc5a269", commits[1].SHA) +} + +// TODO add tests for already merged PR and closed PR diff --git a/tests/integration/api_pull_review_test.go b/tests/integration/api_pull_review_test.go new file mode 100644 index 0000000000..6ebad106fb --- /dev/null +++ b/tests/integration/api_pull_review_test.go @@ -0,0 +1,307 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/json" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIPullReview(t *testing.T) { + defer tests.PrepareTestEnv(t)() + pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3}) + assert.NoError(t, pullIssue.LoadAttributes(db.DefaultContext)) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue.RepoID}) + + // test ListPullReviews + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token) + resp := session.MakeRequest(t, req, http.StatusOK) + + var reviews []*api.PullReview + DecodeJSON(t, resp, &reviews) + if !assert.Len(t, reviews, 6) { + return + } + for _, r := range reviews { + assert.EqualValues(t, pullIssue.HTMLURL(), r.HTMLPullURL) + } + assert.EqualValues(t, 8, reviews[3].ID) + assert.EqualValues(t, "APPROVED", reviews[3].State) + assert.EqualValues(t, 0, reviews[3].CodeCommentsCount) + assert.True(t, reviews[3].Stale) + assert.False(t, reviews[3].Official) + + assert.EqualValues(t, 10, reviews[5].ID) + assert.EqualValues(t, "REQUEST_CHANGES", reviews[5].State) + assert.EqualValues(t, 1, reviews[5].CodeCommentsCount) + assert.EqualValues(t, -1, reviews[5].Reviewer.ID) // ghost user + assert.False(t, reviews[5].Stale) + assert.True(t, reviews[5].Official) + + // test GetPullReview + req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, reviews[3].ID, token) + resp = session.MakeRequest(t, req, http.StatusOK) + var review api.PullReview + DecodeJSON(t, resp, &review) + assert.EqualValues(t, *reviews[3], review) + + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/%d/reviews/%d?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, reviews[5].ID, token) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &review) + assert.EqualValues(t, *reviews[5], review) + + // test GetPullReviewComments + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 7}) + req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d/comments?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, 10, token) + resp = session.MakeRequest(t, req, http.StatusOK) + var reviewComments []*api.PullReviewComment + DecodeJSON(t, resp, &reviewComments) + assert.Len(t, reviewComments, 1) + assert.EqualValues(t, "Ghost", reviewComments[0].Poster.UserName) + assert.EqualValues(t, "a review from a deleted user", reviewComments[0].Body) + assert.EqualValues(t, comment.ID, reviewComments[0].ID) + assert.EqualValues(t, comment.UpdatedUnix, reviewComments[0].Updated.Unix()) + assert.EqualValues(t, comment.HTMLURL(), reviewComments[0].HTMLURL) + + // test CreatePullReview + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.CreatePullReviewOptions{ + Body: "body1", + // Event: "" # will result in PENDING + Comments: []api.CreatePullReviewComment{ + { + Path: "README.md", + Body: "first new line", + OldLineNum: 0, + NewLineNum: 1, + }, { + Path: "README.md", + Body: "first old line", + OldLineNum: 1, + NewLineNum: 0, + }, { + Path: "iso-8859-1.txt", + Body: "this line contains a non-utf-8 character", + OldLineNum: 0, + NewLineNum: 1, + }, + }, + }) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &review) + assert.EqualValues(t, 6, review.ID) + assert.EqualValues(t, "PENDING", review.State) + assert.EqualValues(t, 3, review.CodeCommentsCount) + + // test SubmitPullReview + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, review.ID, token), &api.SubmitPullReviewOptions{ + Event: "APPROVED", + Body: "just two nits", + }) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &review) + assert.EqualValues(t, 6, review.ID) + assert.EqualValues(t, "APPROVED", review.State) + assert.EqualValues(t, 3, review.CodeCommentsCount) + + // test dismiss review + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d/dismissals?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, review.ID, token), &api.DismissPullReviewOptions{ + Message: "test", + }) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &review) + assert.EqualValues(t, 6, review.ID) + assert.True(t, review.Dismissed) + + // test dismiss review + req = NewRequest(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d/undismissals?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, review.ID, token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &review) + assert.EqualValues(t, 6, review.ID) + assert.False(t, review.Dismissed) + + // test DeletePullReview + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.CreatePullReviewOptions{ + Body: "just a comment", + Event: "COMMENT", + }) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &review) + assert.EqualValues(t, "COMMENT", review.State) + assert.EqualValues(t, 0, review.CodeCommentsCount) + req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, review.ID, token) + session.MakeRequest(t, req, http.StatusNoContent) + + // test CreatePullReview Comment without body but with comments + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.CreatePullReviewOptions{ + // Body: "", + Event: "COMMENT", + Comments: []api.CreatePullReviewComment{ + { + Path: "README.md", + Body: "first new line", + OldLineNum: 0, + NewLineNum: 1, + }, { + Path: "README.md", + Body: "first old line", + OldLineNum: 1, + NewLineNum: 0, + }, + }, + }) + var commentReview api.PullReview + + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &commentReview) + assert.EqualValues(t, "COMMENT", commentReview.State) + assert.EqualValues(t, 2, commentReview.CodeCommentsCount) + assert.EqualValues(t, "", commentReview.Body) + assert.EqualValues(t, false, commentReview.Dismissed) + + // test CreatePullReview Comment with body but without comments + commentBody := "This is a body of the comment." + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.CreatePullReviewOptions{ + Body: commentBody, + Event: "COMMENT", + Comments: []api.CreatePullReviewComment{}, + }) + + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &commentReview) + assert.EqualValues(t, "COMMENT", commentReview.State) + assert.EqualValues(t, 0, commentReview.CodeCommentsCount) + assert.EqualValues(t, commentBody, commentReview.Body) + assert.EqualValues(t, false, commentReview.Dismissed) + + // test CreatePullReview Comment without body and no comments + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.CreatePullReviewOptions{ + Body: "", + Event: "COMMENT", + Comments: []api.CreatePullReviewComment{}, + }) + resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity) + errMap := make(map[string]interface{}) + json.Unmarshal(resp.Body.Bytes(), &errMap) + assert.EqualValues(t, "review event COMMENT requires a body or a comment", errMap["message"].(string)) + + // test get review requests + // to make it simple, use same api with get review + pullIssue12 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 12}) + assert.NoError(t, pullIssue12.LoadAttributes(db.DefaultContext)) + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue12.RepoID}) + + req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &reviews) + assert.EqualValues(t, 11, reviews[0].ID) + assert.EqualValues(t, "REQUEST_REVIEW", reviews[0].State) + assert.EqualValues(t, 0, reviews[0].CodeCommentsCount) + assert.False(t, reviews[0].Stale) + assert.True(t, reviews[0].Official) + assert.EqualValues(t, "test_team", reviews[0].ReviewerTeam.Name) + + assert.EqualValues(t, 12, reviews[1].ID) + assert.EqualValues(t, "REQUEST_REVIEW", reviews[1].State) + assert.EqualValues(t, 0, reviews[0].CodeCommentsCount) + assert.False(t, reviews[1].Stale) + assert.True(t, reviews[1].Official) + assert.EqualValues(t, 1, reviews[1].Reviewer.ID) +} + +func TestAPIPullReviewRequest(t *testing.T) { + defer tests.PrepareTestEnv(t)() + pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3}) + assert.NoError(t, pullIssue.LoadAttributes(db.DefaultContext)) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue.RepoID}) + + // Test add Review Request + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{ + Reviewers: []string{"user4@example.com", "user8"}, + }) + session.MakeRequest(t, req, http.StatusCreated) + + // poster of pr can't be reviewer + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{ + Reviewers: []string{"user1"}, + }) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + // test user not exist + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{ + Reviewers: []string{"testOther"}, + }) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test Remove Review Request + session2 := loginUser(t, "user4") + token2 := getTokenForLoggedInUser(t, session2) + + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token2), &api.PullReviewRequestOptions{ + Reviewers: []string{"user4"}, + }) + session.MakeRequest(t, req, http.StatusNoContent) + + // doer is not admin + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token2), &api.PullReviewRequestOptions{ + Reviewers: []string{"user8"}, + }) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{ + Reviewers: []string{"user8"}, + }) + session.MakeRequest(t, req, http.StatusNoContent) + + // Test team review request + pullIssue12 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 12}) + assert.NoError(t, pullIssue12.LoadAttributes(db.DefaultContext)) + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue12.RepoID}) + + // Test add Team Review Request + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{ + TeamReviewers: []string{"team1", "owners"}, + }) + session.MakeRequest(t, req, http.StatusCreated) + + // Test add Team Review Request to not allowned + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{ + TeamReviewers: []string{"test_team"}, + }) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + // Test add Team Review Request to not exist + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{ + TeamReviewers: []string{"not_exist_team"}, + }) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test Remove team Review Request + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{ + TeamReviewers: []string{"team1"}, + }) + session.MakeRequest(t, req, http.StatusNoContent) + + // empty request test + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{}) + session.MakeRequest(t, req, http.StatusCreated) + + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{}) + session.MakeRequest(t, req, http.StatusNoContent) +} diff --git a/tests/integration/api_pull_test.go b/tests/integration/api_pull_test.go new file mode 100644 index 0000000000..032912a073 --- /dev/null +++ b/tests/integration/api_pull_test.go @@ -0,0 +1,185 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/services/forms" + issue_service "code.gitea.io/gitea/services/issue" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIViewPulls(t *testing.T) { + defer tests.PrepareTestEnv(t)() + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls?state=all&token="+token, owner.Name, repo.Name) + resp := session.MakeRequest(t, req, http.StatusOK) + + var pulls []*api.PullRequest + DecodeJSON(t, resp, &pulls) + expectedLen := unittest.GetCount(t, &issues_model.Issue{RepoID: repo.ID}, unittest.Cond("is_pull = ?", true)) + assert.Len(t, pulls, expectedLen) +} + +// TestAPIMergePullWIP ensures that we can't merge a WIP pull request +func TestAPIMergePullWIP(t *testing.T) { + defer tests.PrepareTestEnv(t)() + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{Status: issues_model.PullRequestStatusMergeable}, unittest.Cond("has_merged = ?", false)) + pr.LoadIssue() + issue_service.ChangeTitle(pr.Issue, owner, setting.Repository.PullRequest.WorkInProgressPrefixes[0]+" "+pr.Issue.Title) + + // force reload + pr.LoadAttributes() + + assert.Contains(t, pr.Issue.Title, setting.Repository.PullRequest.WorkInProgressPrefixes[0]) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s", owner.Name, repo.Name, pr.Index, token), &forms.MergePullRequestForm{ + MergeMessageField: pr.Issue.Title, + Do: string(repo_model.MergeStyleMerge), + }) + + session.MakeRequest(t, req, http.StatusMethodNotAllowed) +} + +func TestAPICreatePullSuccess(t *testing.T) { + defer tests.PrepareTestEnv(t)() + repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) + // repo10 have code, pulls units. + repo11 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11}) + // repo11 only have code unit but should still create pulls + owner10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo10.OwnerID}) + owner11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo11.OwnerID}) + + session := loginUser(t, owner11.Name) + token := getTokenForLoggedInUser(t, session) + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", owner10.Name, repo10.Name, token), &api.CreatePullRequestOption{ + Head: fmt.Sprintf("%s:master", owner11.Name), + Base: "master", + Title: "create a failure pr", + }) + session.MakeRequest(t, req, http.StatusCreated) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) // second request should fail +} + +func TestAPICreatePullWithFieldsSuccess(t *testing.T) { + defer tests.PrepareTestEnv(t)() + // repo10 have code, pulls units. + repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) + owner10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo10.OwnerID}) + // repo11 only have code unit but should still create pulls + repo11 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11}) + owner11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo11.OwnerID}) + + session := loginUser(t, owner11.Name) + token := getTokenForLoggedInUser(t, session) + + opts := &api.CreatePullRequestOption{ + Head: fmt.Sprintf("%s:master", owner11.Name), + Base: "master", + Title: "create a failure pr", + Body: "foobaaar", + Milestone: 5, + Assignees: []string{owner10.Name}, + Labels: []int64{5}, + } + + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", owner10.Name, repo10.Name, token), opts) + + res := session.MakeRequest(t, req, http.StatusCreated) + pull := new(api.PullRequest) + DecodeJSON(t, res, pull) + + assert.NotNil(t, pull.Milestone) + assert.EqualValues(t, opts.Milestone, pull.Milestone.ID) + if assert.Len(t, pull.Assignees, 1) { + assert.EqualValues(t, opts.Assignees[0], owner10.Name) + } + assert.NotNil(t, pull.Labels) + assert.EqualValues(t, opts.Labels[0], pull.Labels[0].ID) +} + +func TestAPICreatePullWithFieldsFailure(t *testing.T) { + defer tests.PrepareTestEnv(t)() + // repo10 have code, pulls units. + repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) + owner10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo10.OwnerID}) + // repo11 only have code unit but should still create pulls + repo11 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11}) + owner11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo11.OwnerID}) + + session := loginUser(t, owner11.Name) + token := getTokenForLoggedInUser(t, session) + + opts := &api.CreatePullRequestOption{ + Head: fmt.Sprintf("%s:master", owner11.Name), + Base: "master", + } + + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", owner10.Name, repo10.Name, token), opts) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + opts.Title = "is required" + + opts.Milestone = 666 + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + opts.Milestone = 5 + + opts.Assignees = []string{"qweruqweroiuyqweoiruywqer"} + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + opts.Assignees = []string{owner10.LoginName} + + opts.Labels = []int64{55555} + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + opts.Labels = []int64{5} +} + +func TestAPIEditPull(t *testing.T) { + defer tests.PrepareTestEnv(t)() + repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) + owner10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo10.OwnerID}) + + session := loginUser(t, owner10.Name) + token := getTokenForLoggedInUser(t, session) + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", owner10.Name, repo10.Name, token), &api.CreatePullRequestOption{ + Head: "develop", + Base: "master", + Title: "create a success pr", + }) + pull := new(api.PullRequest) + resp := session.MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, pull) + assert.EqualValues(t, "master", pull.Base.Name) + + req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d?token=%s", owner10.Name, repo10.Name, pull.Index, token), &api.EditPullRequestOption{ + Base: "feature/1", + Title: "edit a this pr", + }) + resp = session.MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, pull) + assert.EqualValues(t, "feature/1", pull.Base.Name) + + req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d?token=%s", owner10.Name, repo10.Name, pull.Index, token), &api.EditPullRequestOption{ + Base: "not-exist", + }) + session.MakeRequest(t, req, http.StatusNotFound) +} diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go new file mode 100644 index 0000000000..0c7f5e2d52 --- /dev/null +++ b/tests/integration/api_releases_test.go @@ -0,0 +1,233 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIListReleases(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + token := getUserToken(t, user2.LowerName) + + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/releases", user2.Name, repo.Name)) + link.RawQuery = url.Values{"token": {token}}.Encode() + resp := MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) + var apiReleases []*api.Release + DecodeJSON(t, resp, &apiReleases) + if assert.Len(t, apiReleases, 3) { + for _, release := range apiReleases { + switch release.ID { + case 1: + assert.False(t, release.IsDraft) + assert.False(t, release.IsPrerelease) + case 4: + assert.True(t, release.IsDraft) + assert.False(t, release.IsPrerelease) + case 5: + assert.False(t, release.IsDraft) + assert.True(t, release.IsPrerelease) + default: + assert.NoError(t, fmt.Errorf("unexpected release: %v", release)) + } + } + } + + // test filter + testFilterByLen := func(auth bool, query url.Values, expectedLength int, msgAndArgs ...string) { + if auth { + query.Set("token", token) + } + link.RawQuery = query.Encode() + resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) + DecodeJSON(t, resp, &apiReleases) + assert.Len(t, apiReleases, expectedLength, msgAndArgs) + } + + testFilterByLen(false, url.Values{"draft": {"true"}}, 0, "anon should not see drafts") + testFilterByLen(true, url.Values{"draft": {"true"}}, 1, "repo owner should see drafts") + testFilterByLen(true, url.Values{"draft": {"false"}}, 2, "exclude drafts") + testFilterByLen(true, url.Values{"draft": {"false"}, "pre-release": {"false"}}, 1, "exclude drafts and pre-releases") + testFilterByLen(true, url.Values{"pre-release": {"true"}}, 1, "only get pre-release") + testFilterByLen(true, url.Values{"draft": {"true"}, "pre-release": {"true"}}, 0, "there is no pre-release draft") +} + +func createNewReleaseUsingAPI(t *testing.T, session *TestSession, token string, owner *user_model.User, repo *repo_model.Repository, name, target, title, desc string) *api.Release { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases?token=%s", + owner.Name, repo.Name, token) + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateReleaseOption{ + TagName: name, + Title: title, + Note: desc, + IsDraft: false, + IsPrerelease: false, + Target: target, + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + + var newRelease api.Release + DecodeJSON(t, resp, &newRelease) + rel := &repo_model.Release{ + ID: newRelease.ID, + TagName: newRelease.TagName, + Title: newRelease.Title, + } + unittest.AssertExistsAndLoadBean(t, rel) + assert.EqualValues(t, newRelease.Note, rel.Note) + + return &newRelease +} + +func TestAPICreateAndUpdateRelease(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, owner.LowerName) + token := getTokenForLoggedInUser(t, session) + + gitRepo, err := git.OpenRepository(git.DefaultContext, repo.RepoPath()) + assert.NoError(t, err) + defer gitRepo.Close() + + err = gitRepo.CreateTag("v0.0.1", "master") + assert.NoError(t, err) + + target, err := gitRepo.GetTagCommitID("v0.0.1") + assert.NoError(t, err) + + newRelease := createNewReleaseUsingAPI(t, session, token, owner, repo, "v0.0.1", target, "v0.0.1", "test") + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d?token=%s", + owner.Name, repo.Name, newRelease.ID, token) + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + + var release api.Release + DecodeJSON(t, resp, &release) + + assert.Equal(t, newRelease.TagName, release.TagName) + assert.Equal(t, newRelease.Title, release.Title) + assert.Equal(t, newRelease.Note, release.Note) + + req = NewRequestWithJSON(t, "PATCH", urlStr, &api.EditReleaseOption{ + TagName: release.TagName, + Title: release.Title, + Note: "updated", + IsDraft: &release.IsDraft, + IsPrerelease: &release.IsPrerelease, + Target: release.Target, + }) + resp = session.MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &newRelease) + rel := &repo_model.Release{ + ID: newRelease.ID, + TagName: newRelease.TagName, + Title: newRelease.Title, + } + unittest.AssertExistsAndLoadBean(t, rel) + assert.EqualValues(t, rel.Note, newRelease.Note) +} + +func TestAPICreateReleaseToDefaultBranch(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, owner.LowerName) + token := getTokenForLoggedInUser(t, session) + + createNewReleaseUsingAPI(t, session, token, owner, repo, "v0.0.1", "", "v0.0.1", "test") +} + +func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, owner.LowerName) + token := getTokenForLoggedInUser(t, session) + + gitRepo, err := git.OpenRepository(git.DefaultContext, repo.RepoPath()) + assert.NoError(t, err) + defer gitRepo.Close() + + err = gitRepo.CreateTag("v0.0.1", "master") + assert.NoError(t, err) + + createNewReleaseUsingAPI(t, session, token, owner, repo, "v0.0.1", "", "v0.0.1", "test") +} + +func TestAPIGetReleaseByTag(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, owner.LowerName) + + tag := "v1.1" + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", + owner.Name, repo.Name, tag) + + req := NewRequestf(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + + var release *api.Release + DecodeJSON(t, resp, &release) + + assert.Equal(t, "testing-release", release.Title) + + nonexistingtag := "nonexistingtag" + + urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", + owner.Name, repo.Name, nonexistingtag) + + req = NewRequestf(t, "GET", urlStr) + resp = session.MakeRequest(t, req, http.StatusNotFound) + + var err *api.APIError + DecodeJSON(t, resp, &err) + assert.NotEmpty(t, err.Message) +} + +func TestAPIDeleteReleaseByTagName(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, owner.LowerName) + token := getTokenForLoggedInUser(t, session) + + createNewReleaseUsingAPI(t, session, token, owner, repo, "release-tag", "", "Release Tag", "test") + + // delete release + req := NewRequestf(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/release-tag?token=%s", owner.Name, repo.Name, token)) + _ = session.MakeRequest(t, req, http.StatusNoContent) + + // make sure release is deleted + req = NewRequestf(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/release-tag?token=%s", owner.Name, repo.Name, token)) + _ = session.MakeRequest(t, req, http.StatusNotFound) + + // delete release tag too + req = NewRequestf(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/tags/release-tag?token=%s", owner.Name, repo.Name, token)) + _ = session.MakeRequest(t, req, http.StatusNoContent) +} diff --git a/tests/integration/api_repo_archive_test.go b/tests/integration/api_repo_archive_test.go new file mode 100644 index 0000000000..3707cb7c1c --- /dev/null +++ b/tests/integration/api_repo_archive_test.go @@ -0,0 +1,54 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "io" + "net/http" + "net/url" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIDownloadArchive(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.LowerName) + token := getTokenForLoggedInUser(t, session) + + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master.zip", user2.Name, repo.Name)) + link.RawQuery = url.Values{"token": {token}}.Encode() + resp := MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) + bs, err := io.ReadAll(resp.Body) + assert.NoError(t, err) + assert.EqualValues(t, 320, len(bs)) + + link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master.tar.gz", user2.Name, repo.Name)) + link.RawQuery = url.Values{"token": {token}}.Encode() + resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + assert.EqualValues(t, 266, len(bs)) + + link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master.bundle", user2.Name, repo.Name)) + link.RawQuery = url.Values{"token": {token}}.Encode() + resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + assert.EqualValues(t, 382, len(bs)) + + link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master", user2.Name, repo.Name)) + link.RawQuery = url.Values{"token": {token}}.Encode() + MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusBadRequest) +} diff --git a/tests/integration/api_repo_collaborator_test.go b/tests/integration/api_repo_collaborator_test.go new file mode 100644 index 0000000000..3527e16572 --- /dev/null +++ b/tests/integration/api_repo_collaborator_test.go @@ -0,0 +1,131 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "testing" + + "code.gitea.io/gitea/models/perm" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func TestAPIRepoCollaboratorPermission(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + repo2Owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID}) + + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + user10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10}) + user11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 11}) + + session := loginUser(t, repo2Owner.Name) + testCtx := NewAPITestContext(t, repo2Owner.Name, repo2.Name) + + t.Run("RepoOwnerShouldBeOwner", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, repo2Owner.Name, testCtx.Token) + resp := session.MakeRequest(t, req, http.StatusOK) + + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) + + assert.Equal(t, "owner", repoPermission.Permission) + }) + + t.Run("CollaboratorWithReadAccess", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeRead)) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, user4.Name, testCtx.Token) + resp := session.MakeRequest(t, req, http.StatusOK) + + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) + + assert.Equal(t, "read", repoPermission.Permission) + }) + + t.Run("CollaboratorWithWriteAccess", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithWriteAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeWrite)) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, user4.Name, testCtx.Token) + resp := session.MakeRequest(t, req, http.StatusOK) + + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) + + assert.Equal(t, "write", repoPermission.Permission) + }) + + t.Run("CollaboratorWithAdminAccess", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeAdmin)) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, user4.Name, testCtx.Token) + resp := session.MakeRequest(t, req, http.StatusOK) + + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) + + assert.Equal(t, "admin", repoPermission.Permission) + }) + + t.Run("CollaboratorNotFound", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, "non-existent-user", testCtx.Token) + session.MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) + + _session := loginUser(t, user5.Name) + _testCtx := NewAPITestContext(t, user5.Name, repo2.Name) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, user5.Name, _testCtx.Token) + resp := _session.MakeRequest(t, req, http.StatusOK) + + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) + + assert.Equal(t, "read", repoPermission.Permission) + }) + + t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) + + _session := loginUser(t, user5.Name) + _testCtx := NewAPITestContext(t, user5.Name, repo2.Name) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, user5.Name, _testCtx.Token) + resp := _session.MakeRequest(t, req, http.StatusOK) + + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) + + assert.Equal(t, "read", repoPermission.Permission) + }) + + t.Run("RepoAdminCanQueryACollaboratorsPermissions", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user10.Name, perm.AccessModeAdmin)) + t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user11.Name, perm.AccessModeRead)) + + _session := loginUser(t, user10.Name) + _testCtx := NewAPITestContext(t, user10.Name, repo2.Name) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, user11.Name, _testCtx.Token) + resp := _session.MakeRequest(t, req, http.StatusOK) + + var repoPermission api.RepoCollaboratorPermission + DecodeJSON(t, resp, &repoPermission) + + assert.Equal(t, "read", repoPermission.Permission) + }) + }) +} diff --git a/tests/integration/api_repo_edit_test.go b/tests/integration/api_repo_edit_test.go new file mode 100644 index 0000000000..5ef92bf47c --- /dev/null +++ b/tests/integration/api_repo_edit_test.go @@ -0,0 +1,346 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + unit_model "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +// getRepoEditOptionFromRepo gets the options for an existing repo exactly as is +func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption { + name := repo.Name + description := repo.Description + website := repo.Website + private := repo.IsPrivate + hasIssues := false + var internalTracker *api.InternalTracker + var externalTracker *api.ExternalTracker + if unit, err := repo.GetUnit(unit_model.TypeIssues); err == nil { + config := unit.IssuesConfig() + hasIssues = true + internalTracker = &api.InternalTracker{ + EnableTimeTracker: config.EnableTimetracker, + AllowOnlyContributorsToTrackTime: config.AllowOnlyContributorsToTrackTime, + EnableIssueDependencies: config.EnableDependencies, + } + } else if unit, err := repo.GetUnit(unit_model.TypeExternalTracker); err == nil { + config := unit.ExternalTrackerConfig() + hasIssues = true + externalTracker = &api.ExternalTracker{ + ExternalTrackerURL: config.ExternalTrackerURL, + ExternalTrackerFormat: config.ExternalTrackerFormat, + ExternalTrackerStyle: config.ExternalTrackerStyle, + } + } + hasWiki := false + var externalWiki *api.ExternalWiki + if _, err := repo.GetUnit(unit_model.TypeWiki); err == nil { + hasWiki = true + } else if unit, err := repo.GetUnit(unit_model.TypeExternalWiki); err == nil { + hasWiki = true + config := unit.ExternalWikiConfig() + externalWiki = &api.ExternalWiki{ + ExternalWikiURL: config.ExternalWikiURL, + } + } + defaultBranch := repo.DefaultBranch + hasPullRequests := false + ignoreWhitespaceConflicts := false + allowMerge := false + allowRebase := false + allowRebaseMerge := false + allowSquash := false + if unit, err := repo.GetUnit(unit_model.TypePullRequests); err == nil { + config := unit.PullRequestsConfig() + hasPullRequests = true + ignoreWhitespaceConflicts = config.IgnoreWhitespaceConflicts + allowMerge = config.AllowMerge + allowRebase = config.AllowRebase + allowRebaseMerge = config.AllowRebaseMerge + allowSquash = config.AllowSquash + } + archived := repo.IsArchived + return &api.EditRepoOption{ + Name: &name, + Description: &description, + Website: &website, + Private: &private, + HasIssues: &hasIssues, + ExternalTracker: externalTracker, + InternalTracker: internalTracker, + HasWiki: &hasWiki, + ExternalWiki: externalWiki, + DefaultBranch: &defaultBranch, + HasPullRequests: &hasPullRequests, + IgnoreWhitespaceConflicts: &ignoreWhitespaceConflicts, + AllowMerge: &allowMerge, + AllowRebase: &allowRebase, + AllowRebaseMerge: &allowRebaseMerge, + AllowSquash: &allowSquash, + Archived: &archived, + } +} + +// getNewRepoEditOption Gets the options to change everything about an existing repo by adding to strings or changing +// the boolean +func getNewRepoEditOption(opts *api.EditRepoOption) *api.EditRepoOption { + // Gives a new property to everything + name := *opts.Name + "renamed" + description := "new description" + website := "http://wwww.newwebsite.com" + private := !*opts.Private + hasIssues := !*opts.HasIssues + hasWiki := !*opts.HasWiki + defaultBranch := "master" + hasPullRequests := !*opts.HasPullRequests + ignoreWhitespaceConflicts := !*opts.IgnoreWhitespaceConflicts + allowMerge := !*opts.AllowMerge + allowRebase := !*opts.AllowRebase + allowRebaseMerge := !*opts.AllowRebaseMerge + allowSquash := !*opts.AllowSquash + archived := !*opts.Archived + + return &api.EditRepoOption{ + Name: &name, + Description: &description, + Website: &website, + Private: &private, + DefaultBranch: &defaultBranch, + HasIssues: &hasIssues, + HasWiki: &hasWiki, + HasPullRequests: &hasPullRequests, + IgnoreWhitespaceConflicts: &ignoreWhitespaceConflicts, + AllowMerge: &allowMerge, + AllowRebase: &allowRebase, + AllowRebaseMerge: &allowRebaseMerge, + AllowSquash: &allowSquash, + Archived: &archived, + } +} + +func TestAPIRepoEdit(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + bFalse, bTrue := false, true + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo + repo15 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 15}) // empty repo + repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo + + // Get user2's token + session := loginUser(t, user2.Name) + token2 := getTokenForLoggedInUser(t, session) + // Get user4's token + session = loginUser(t, user4.Name) + token4 := getTokenForLoggedInUser(t, session) + session = emptyTestSession(t) + + // Test editing a repo1 which user2 owns, changing name and many properties + origRepoEditOption := getRepoEditOptionFromRepo(repo1) + repoEditOption := getNewRepoEditOption(origRepoEditOption) + url := fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo1.Name, token2) + req := NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + resp := session.MakeRequest(t, req, http.StatusOK) + var repo api.Repository + DecodeJSON(t, resp, &repo) + assert.NotNil(t, repo) + // check response + assert.Equal(t, *repoEditOption.Name, repo.Name) + assert.Equal(t, *repoEditOption.Description, repo.Description) + assert.Equal(t, *repoEditOption.Website, repo.Website) + assert.Equal(t, *repoEditOption.Archived, repo.Archived) + // check repo1 from database + repo1edited := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + repo1editedOption := getRepoEditOptionFromRepo(repo1edited) + assert.Equal(t, *repoEditOption.Name, *repo1editedOption.Name) + assert.Equal(t, *repoEditOption.Description, *repo1editedOption.Description) + assert.Equal(t, *repoEditOption.Website, *repo1editedOption.Website) + assert.Equal(t, *repoEditOption.Archived, *repo1editedOption.Archived) + assert.Equal(t, *repoEditOption.Private, *repo1editedOption.Private) + assert.Equal(t, *repoEditOption.HasWiki, *repo1editedOption.HasWiki) + + // Test editing repo1 to use internal issue and wiki (default) + *repoEditOption.HasIssues = true + repoEditOption.ExternalTracker = nil + repoEditOption.InternalTracker = &api.InternalTracker{ + EnableTimeTracker: false, + AllowOnlyContributorsToTrackTime: false, + EnableIssueDependencies: false, + } + *repoEditOption.HasWiki = true + repoEditOption.ExternalWiki = nil + url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2) + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &repo) + assert.NotNil(t, repo) + // check repo1 was written to database + repo1edited = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + repo1editedOption = getRepoEditOptionFromRepo(repo1edited) + assert.Equal(t, *repo1editedOption.HasIssues, true) + assert.Nil(t, repo1editedOption.ExternalTracker) + assert.Equal(t, *repo1editedOption.InternalTracker, *repoEditOption.InternalTracker) + assert.Equal(t, *repo1editedOption.HasWiki, true) + assert.Nil(t, repo1editedOption.ExternalWiki) + + // Test editing repo1 to use external issue and wiki + repoEditOption.ExternalTracker = &api.ExternalTracker{ + ExternalTrackerURL: "http://www.somewebsite.com", + ExternalTrackerFormat: "http://www.somewebsite.com/{user}/{repo}?issue={index}", + ExternalTrackerStyle: "alphanumeric", + } + repoEditOption.ExternalWiki = &api.ExternalWiki{ + ExternalWikiURL: "http://www.somewebsite.com", + } + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &repo) + assert.NotNil(t, repo) + // check repo1 was written to database + repo1edited = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + repo1editedOption = getRepoEditOptionFromRepo(repo1edited) + assert.Equal(t, *repo1editedOption.HasIssues, true) + assert.Equal(t, *repo1editedOption.ExternalTracker, *repoEditOption.ExternalTracker) + assert.Equal(t, *repo1editedOption.HasWiki, true) + assert.Equal(t, *repo1editedOption.ExternalWiki, *repoEditOption.ExternalWiki) + + // Do some tests with invalid URL for external tracker and wiki + repoEditOption.ExternalTracker.ExternalTrackerURL = "htp://www.somewebsite.com" + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + repoEditOption.ExternalTracker.ExternalTrackerURL = "http://www.somewebsite.com" + repoEditOption.ExternalTracker.ExternalTrackerFormat = "http://www.somewebsite.com/{user/{repo}?issue={index}" + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + repoEditOption.ExternalTracker.ExternalTrackerFormat = "http://www.somewebsite.com/{user}/{repo}?issue={index}" + repoEditOption.ExternalWiki.ExternalWikiURL = "htp://www.somewebsite.com" + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + // Test small repo change through API with issue and wiki option not set; They shall not be touched. + *repoEditOption.Description = "small change" + repoEditOption.HasIssues = nil + repoEditOption.ExternalTracker = nil + repoEditOption.HasWiki = nil + repoEditOption.ExternalWiki = nil + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &repo) + assert.NotNil(t, repo) + // check repo1 was written to database + repo1edited = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + repo1editedOption = getRepoEditOptionFromRepo(repo1edited) + assert.Equal(t, *repo1editedOption.Description, *repoEditOption.Description) + assert.Equal(t, *repo1editedOption.HasIssues, true) + assert.NotNil(t, *repo1editedOption.ExternalTracker) + assert.Equal(t, *repo1editedOption.HasWiki, true) + assert.NotNil(t, *repo1editedOption.ExternalWiki) + + // reset repo in db + url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2) + req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption) + _ = session.MakeRequest(t, req, http.StatusOK) + + // Test editing a non-existing repo + name := "repodoesnotexist" + url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, name, token2) + req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{Name: &name}) + _ = session.MakeRequest(t, req, http.StatusNotFound) + + // Test editing repo16 by user4 who does not have write access + origRepoEditOption = getRepoEditOptionFromRepo(repo16) + repoEditOption = getNewRepoEditOption(origRepoEditOption) + url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token4) + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + session.MakeRequest(t, req, http.StatusNotFound) + + // Tests a repo with no token given so will fail + origRepoEditOption = getRepoEditOptionFromRepo(repo16) + repoEditOption = getNewRepoEditOption(origRepoEditOption) + url = fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo16.Name) + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + _ = session.MakeRequest(t, req, http.StatusNotFound) + + // Test using access token for a private repo that the user of the token owns + origRepoEditOption = getRepoEditOptionFromRepo(repo16) + repoEditOption = getNewRepoEditOption(origRepoEditOption) + url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token2) + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + _ = session.MakeRequest(t, req, http.StatusOK) + // reset repo in db + url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2) + req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption) + _ = session.MakeRequest(t, req, http.StatusOK) + + // Test making a repo public that is private + repo16 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) + assert.True(t, repo16.IsPrivate) + repoEditOption = &api.EditRepoOption{ + Private: &bFalse, + } + url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token2) + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + _ = session.MakeRequest(t, req, http.StatusOK) + repo16 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) + assert.False(t, repo16.IsPrivate) + // Make it private again + repoEditOption.Private = &bTrue + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + _ = session.MakeRequest(t, req, http.StatusOK) + + // Test to change empty repo + assert.False(t, repo15.IsArchived) + url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo15.Name, token2) + req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{ + Archived: &bTrue, + }) + _ = session.MakeRequest(t, req, http.StatusOK) + repo15 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 15}) + assert.True(t, repo15.IsArchived) + req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{ + Archived: &bFalse, + }) + _ = session.MakeRequest(t, req, http.StatusOK) + + // Test using org repo "user3/repo3" where user2 is a collaborator + origRepoEditOption = getRepoEditOptionFromRepo(repo3) + repoEditOption = getNewRepoEditOption(origRepoEditOption) + url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user3.Name, repo3.Name, token2) + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + session.MakeRequest(t, req, http.StatusOK) + // reset repo in db + url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user3.Name, *repoEditOption.Name, token2) + req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption) + _ = session.MakeRequest(t, req, http.StatusOK) + + // Test using org repo "user3/repo3" with no user token + origRepoEditOption = getRepoEditOptionFromRepo(repo3) + repoEditOption = getNewRepoEditOption(origRepoEditOption) + url = fmt.Sprintf("/api/v1/repos/%s/%s", user3.Name, repo3.Name) + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test using repo "user2/repo1" where user4 is a NOT collaborator + origRepoEditOption = getRepoEditOptionFromRepo(repo1) + repoEditOption = getNewRepoEditOption(origRepoEditOption) + url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo1.Name, token4) + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + session.MakeRequest(t, req, http.StatusForbidden) + }) +} diff --git a/tests/integration/api_repo_file_create_test.go b/tests/integration/api_repo_file_create_test.go new file mode 100644 index 0000000000..f03efaa0ea --- /dev/null +++ b/tests/integration/api_repo_file_create_test.go @@ -0,0 +1,308 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + stdCtx "context" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path/filepath" + "testing" + "time" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func getCreateFileOptions() api.CreateFileOptions { + content := "This is new text" + contentEncoded := base64.StdEncoding.EncodeToString([]byte(content)) + return api.CreateFileOptions{ + FileOptions: api.FileOptions{ + BranchName: "master", + NewBranchName: "master", + Message: "Making this new file new/file.txt", + Author: api.Identity{ + Name: "Anne Doe", + Email: "annedoe@example.com", + }, + Committer: api.Identity{ + Name: "John Doe", + Email: "johndoe@example.com", + }, + Dates: api.CommitDateOptions{ + Author: time.Unix(946684810, 0), + Committer: time.Unix(978307190, 0), + }, + }, + Content: contentEncoded, + } +} + +func getExpectedFileResponseForCreate(repoFullName, commitID, treePath, latestCommitSHA string) *api.FileResponse { + sha := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" + encoding := "base64" + content := "VGhpcyBpcyBuZXcgdGV4dA==" + selfURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/contents/" + treePath + "?ref=master" + htmlURL := setting.AppURL + repoFullName + "/src/branch/master/" + treePath + gitURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/git/blobs/" + sha + downloadURL := setting.AppURL + repoFullName + "/raw/branch/master/" + treePath + return &api.FileResponse{ + Content: &api.ContentsResponse{ + Name: filepath.Base(treePath), + Path: treePath, + SHA: sha, + LastCommitSHA: latestCommitSHA, + Size: 16, + Type: "file", + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, + Links: &api.FileLinksResponse{ + Self: &selfURL, + GitURL: &gitURL, + HTMLURL: &htmlURL, + }, + }, + Commit: &api.FileCommitResponse{ + CommitMeta: api.CommitMeta{ + URL: setting.AppURL + "api/v1/repos/" + repoFullName + "/git/commits/" + commitID, + SHA: commitID, + }, + HTMLURL: setting.AppURL + repoFullName + "/commit/" + commitID, + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Anne Doe", + Email: "annedoe@example.com", + }, + Date: "2000-01-01T00:00:10Z", + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "John Doe", + Email: "johndoe@example.com", + }, + Date: "2000-12-31T23:59:50Z", + }, + Message: "Updates README.md\n", + }, + Verification: &api.PayloadCommitVerification{ + Verified: false, + Reason: "gpg.error.not_signed_commit", + Signature: "", + Payload: "", + }, + } +} + +func BenchmarkAPICreateFileSmall(b *testing.B) { + onGiteaRunTB(b, func(t testing.TB, u *url.URL) { + b := t.(*testing.B) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo + + for n := 0; n < b.N; n++ { + treePath := fmt.Sprintf("update/file%d.txt", n) + createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath) + } + }) +} + +func BenchmarkAPICreateFileMedium(b *testing.B) { + data := make([]byte, 10*1024*1024) + + onGiteaRunTB(b, func(t testing.TB, u *url.URL) { + b := t.(*testing.B) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo + + b.ResetTimer() + for n := 0; n < b.N; n++ { + treePath := fmt.Sprintf("update/file%d.txt", n) + copy(data, treePath) + createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath) + } + }) +} + +func TestAPICreateFile(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo + repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo + fileID := 0 + + // Get user2's token + session := loginUser(t, user2.Name) + token2 := getTokenForLoggedInUser(t, session) + // Get user4's token + session = loginUser(t, user4.Name) + token4 := getTokenForLoggedInUser(t, session) + session = emptyTestSession(t) + + // Test creating a file in repo1 which user2 owns, try both with branch and empty branch + for _, branch := range [...]string{ + "master", // Branch + "", // Empty branch + } { + createFileOptions := getCreateFileOptions() + createFileOptions.BranchName = branch + fileID++ + treePath := fmt.Sprintf("new/file%d.txt", fileID) + url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req := NewRequestWithJSON(t, "POST", url, &createFileOptions) + resp := session.MakeRequest(t, req, http.StatusCreated) + gitRepo, _ := git.OpenRepository(stdCtx.Background(), repo1.RepoPath()) + commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName) + latestCommit, _ := gitRepo.GetCommitByPath(treePath) + expectedFileResponse := getExpectedFileResponseForCreate("user2/repo1", commitID, treePath, latestCommit.ID.String()) + var fileResponse api.FileResponse + DecodeJSON(t, resp, &fileResponse) + assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) + assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date) + assert.EqualValues(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name) + assert.EqualValues(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date) + gitRepo.Close() + } + + // Test creating a file in a new branch + createFileOptions := getCreateFileOptions() + createFileOptions.BranchName = repo1.DefaultBranch + createFileOptions.NewBranchName = "new_branch" + fileID++ + treePath := fmt.Sprintf("new/file%d.txt", fileID) + url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req := NewRequestWithJSON(t, "POST", url, &createFileOptions) + resp := session.MakeRequest(t, req, http.StatusCreated) + var fileResponse api.FileResponse + DecodeJSON(t, resp, &fileResponse) + expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" + expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/new/file%d.txt", fileID) + expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID) + assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) + assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) + assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) + assert.EqualValues(t, createFileOptions.Message+"\n", fileResponse.Commit.Message) + + // Test creating a file without a message + createFileOptions = getCreateFileOptions() + createFileOptions.Message = "" + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + resp = session.MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, &fileResponse) + expectedMessage := "Add '" + treePath + "'\n" + assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) + + // Test trying to create a file that already exists, should fail + createFileOptions = getCreateFileOptions() + treePath = "README.md" + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity) + expectedAPIError := context.APIError{ + Message: "repository file already exists [path: " + treePath + "]", + URL: setting.API.SwaggerURL, + } + var apiError context.APIError + DecodeJSON(t, resp, &apiError) + assert.Equal(t, expectedAPIError, apiError) + + // Test creating a file in repo1 by user4 who does not have write access + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Tests a repo with no token given so will fail + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test using access token for a private repo that the user of the token owns + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + session.MakeRequest(t, req, http.StatusCreated) + + // Test using org repo "user3/repo3" where user2 is a collaborator + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + session.MakeRequest(t, req, http.StatusCreated) + + // Test using org repo "user3/repo3" with no user token + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test using repo "user2/repo1" where user4 is a NOT collaborator + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + session.MakeRequest(t, req, http.StatusForbidden) + + // Test creating a file in an empty repository + doAPICreateRepository(NewAPITestContext(t, "user2", "empty-repo"), true)(t) + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, "empty-repo", treePath, token2) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + resp = session.MakeRequest(t, req, http.StatusCreated) + emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "empty-repo"}) // public repo + gitRepo, _ := git.OpenRepository(stdCtx.Background(), emptyRepo.RepoPath()) + commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName) + latestCommit, _ := gitRepo.GetCommitByPath(treePath) + expectedFileResponse := getExpectedFileResponseForCreate("user2/empty-repo", commitID, treePath, latestCommit.ID.String()) + DecodeJSON(t, resp, &fileResponse) + assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) + assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date) + assert.EqualValues(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name) + assert.EqualValues(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date) + gitRepo.Close() + }) +} diff --git a/tests/integration/api_repo_file_delete_test.go b/tests/integration/api_repo_file_delete_test.go new file mode 100644 index 0000000000..2c8b1e381f --- /dev/null +++ b/tests/integration/api_repo_file_delete_test.go @@ -0,0 +1,170 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func getDeleteFileOptions() *api.DeleteFileOptions { + return &api.DeleteFileOptions{ + FileOptions: api.FileOptions{ + BranchName: "master", + NewBranchName: "master", + Message: "Removing the file new/file.txt", + Author: api.Identity{ + Name: "John Doe", + Email: "johndoe@example.com", + }, + Committer: api.Identity{ + Name: "Jane Doe", + Email: "janedoe@example.com", + }, + }, + SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", + } +} + +func TestAPIDeleteFile(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo + repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo + fileID := 0 + + // Get user2's token + session := loginUser(t, user2.Name) + token2 := getTokenForLoggedInUser(t, session) + // Get user4's token + session = loginUser(t, user4.Name) + token4 := getTokenForLoggedInUser(t, session) + session = emptyTestSession(t) + + // Test deleting a file in repo1 which user2 owns, try both with branch and empty branch + for _, branch := range [...]string{ + "master", // Branch + "", // Empty branch + } { + fileID++ + treePath := fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user2, repo1, treePath) + deleteFileOptions := getDeleteFileOptions() + deleteFileOptions.BranchName = branch + url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req := NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + resp := session.MakeRequest(t, req, http.StatusOK) + var fileResponse api.FileResponse + DecodeJSON(t, resp, &fileResponse) + assert.NotNil(t, fileResponse) + assert.Nil(t, fileResponse.Content) + } + + // Test deleting file and making the delete in a new branch + fileID++ + treePath := fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user2, repo1, treePath) + deleteFileOptions := getDeleteFileOptions() + deleteFileOptions.BranchName = repo1.DefaultBranch + deleteFileOptions.NewBranchName = "new_branch" + url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req := NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + resp := session.MakeRequest(t, req, http.StatusOK) + var fileResponse api.FileResponse + DecodeJSON(t, resp, &fileResponse) + assert.NotNil(t, fileResponse) + assert.Nil(t, fileResponse.Content) + assert.EqualValues(t, deleteFileOptions.Message+"\n", fileResponse.Commit.Message) + + // Test deleting file without a message + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user2, repo1, treePath) + deleteFileOptions = getDeleteFileOptions() + deleteFileOptions.Message = "" + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &fileResponse) + expectedMessage := "Delete '" + treePath + "'\n" + assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) + + // Test deleting a file with the wrong SHA + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user2, repo1, treePath) + deleteFileOptions = getDeleteFileOptions() + deleteFileOptions.SHA = "badsha" + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + session.MakeRequest(t, req, http.StatusBadRequest) + + // Test creating a file in repo16 by user4 who does not have write access + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user2, repo16, treePath) + deleteFileOptions = getDeleteFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) + req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Tests a repo with no token given so will fail + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user2, repo16, treePath) + deleteFileOptions = getDeleteFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) + req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test using access token for a private repo that the user of the token owns + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user2, repo16, treePath) + deleteFileOptions = getDeleteFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) + req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + session.MakeRequest(t, req, http.StatusOK) + + // Test using org repo "user3/repo3" where user2 is a collaborator + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user3, repo3, treePath) + deleteFileOptions = getDeleteFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) + req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + session.MakeRequest(t, req, http.StatusOK) + + // Test using org repo "user3/repo3" with no user token + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user3, repo3, treePath) + deleteFileOptions = getDeleteFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) + req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test using repo "user2/repo1" where user4 is a NOT collaborator + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user2, repo1, treePath) + deleteFileOptions = getDeleteFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4) + req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + session.MakeRequest(t, req, http.StatusForbidden) + }) +} diff --git a/tests/integration/api_repo_file_get_test.go b/tests/integration/api_repo_file_get_test.go new file mode 100644 index 0000000000..2a7a5fa634 --- /dev/null +++ b/tests/integration/api_repo_file_get_test.go @@ -0,0 +1,57 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "os" + "testing" + + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIGetRawFileOrLFS(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // Test with raw file + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/README.md") + resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String()) + + // Test with LFS + onGiteaRun(t, func(t *testing.T, u *url.URL) { + httpContext := NewAPITestContext(t, "user2", "repo-lfs-test") + doAPICreateRepository(httpContext, false, func(t *testing.T, repository api.Repository) { + u.Path = httpContext.GitPath() + dstPath, err := os.MkdirTemp("", httpContext.Reponame) + assert.NoError(t, err) + defer util.RemoveAll(dstPath) + + u.Path = httpContext.GitPath() + u.User = url.UserPassword("user2", userPassword) + + t.Run("Clone", doGitClone(dstPath, u)) + + dstPath2, err := os.MkdirTemp("", httpContext.Reponame) + assert.NoError(t, err) + defer util.RemoveAll(dstPath2) + + t.Run("Partial Clone", doPartialGitClone(dstPath2, u)) + + lfs, _ := lfsCommitAndPushTest(t, dstPath) + + reqLFS := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/"+lfs) + respLFS := MakeRequestNilResponseRecorder(t, reqLFS, http.StatusOK) + assert.Equal(t, littleSize, respLFS.Length) + + doAPIDeleteRepository(httpContext) + }) + }) +} diff --git a/tests/integration/api_repo_file_helpers.go b/tests/integration/api_repo_file_helpers.go new file mode 100644 index 0000000000..298bae95c0 --- /dev/null +++ b/tests/integration/api_repo_file_helpers.go @@ -0,0 +1,29 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + api "code.gitea.io/gitea/modules/structs" + files_service "code.gitea.io/gitea/services/repository/files" +) + +func createFileInBranch(user *user_model.User, repo *repo_model.Repository, treePath, branchName, content string) (*api.FileResponse, error) { + opts := &files_service.UpdateRepoFileOptions{ + OldBranch: branchName, + TreePath: treePath, + Content: content, + IsNewFile: true, + Author: nil, + Committer: nil, + } + return files_service.CreateOrUpdateRepoFile(git.DefaultContext, repo, user, opts) +} + +func createFile(user *user_model.User, repo *repo_model.Repository, treePath string) (*api.FileResponse, error) { + return createFileInBranch(user, repo, treePath, repo.DefaultBranch, "This is a NEW file") +} diff --git a/tests/integration/api_repo_file_update_test.go b/tests/integration/api_repo_file_update_test.go new file mode 100644 index 0000000000..a3be67ad84 --- /dev/null +++ b/tests/integration/api_repo_file_update_test.go @@ -0,0 +1,278 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + stdCtx "context" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path/filepath" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func getUpdateFileOptions() *api.UpdateFileOptions { + content := "This is updated text" + contentEncoded := base64.StdEncoding.EncodeToString([]byte(content)) + return &api.UpdateFileOptions{ + DeleteFileOptions: api.DeleteFileOptions{ + FileOptions: api.FileOptions{ + BranchName: "master", + NewBranchName: "master", + Message: "My update of new/file.txt", + Author: api.Identity{ + Name: "John Doe", + Email: "johndoe@example.com", + }, + Committer: api.Identity{ + Name: "Anne Doe", + Email: "annedoe@example.com", + }, + }, + SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", + }, + Content: contentEncoded, + } +} + +func getExpectedFileResponseForUpdate(commitID, treePath, lastCommitSHA string) *api.FileResponse { + sha := "08bd14b2e2852529157324de9c226b3364e76136" + encoding := "base64" + content := "VGhpcyBpcyB1cGRhdGVkIHRleHQ=" + selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master" + htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath + gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha + downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath + return &api.FileResponse{ + Content: &api.ContentsResponse{ + Name: filepath.Base(treePath), + Path: treePath, + SHA: sha, + LastCommitSHA: lastCommitSHA, + Type: "file", + Size: 20, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, + Links: &api.FileLinksResponse{ + Self: &selfURL, + GitURL: &gitURL, + HTMLURL: &htmlURL, + }, + }, + Commit: &api.FileCommitResponse{ + CommitMeta: api.CommitMeta{ + URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + commitID, + SHA: commitID, + }, + HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID, + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "John Doe", + Email: "johndoe@example.com", + }, + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "Anne Doe", + Email: "annedoe@example.com", + }, + }, + Message: "My update of README.md\n", + }, + Verification: &api.PayloadCommitVerification{ + Verified: false, + Reason: "gpg.error.not_signed_commit", + Signature: "", + Payload: "", + }, + } +} + +func TestAPIUpdateFile(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo + repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo + fileID := 0 + + // Get user2's token + session := loginUser(t, user2.Name) + token2 := getTokenForLoggedInUser(t, session) + // Get user4's token + session = loginUser(t, user4.Name) + token4 := getTokenForLoggedInUser(t, session) + session = emptyTestSession(t) + + // Test updating a file in repo1 which user2 owns, try both with branch and empty branch + for _, branch := range [...]string{ + "master", // Branch + "", // Empty branch + } { + fileID++ + treePath := fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + updateFileOptions := getUpdateFileOptions() + updateFileOptions.BranchName = branch + url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req := NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + resp := session.MakeRequest(t, req, http.StatusOK) + gitRepo, _ := git.OpenRepository(stdCtx.Background(), repo1.RepoPath()) + commitID, _ := gitRepo.GetBranchCommitID(updateFileOptions.NewBranchName) + lasCommit, _ := gitRepo.GetCommitByPath(treePath) + expectedFileResponse := getExpectedFileResponseForUpdate(commitID, treePath, lasCommit.ID.String()) + var fileResponse api.FileResponse + DecodeJSON(t, resp, &fileResponse) + assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) + assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + gitRepo.Close() + } + + // Test updating a file in a new branch + updateFileOptions := getUpdateFileOptions() + updateFileOptions.BranchName = repo1.DefaultBranch + updateFileOptions.NewBranchName = "new_branch" + fileID++ + treePath := fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req := NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + resp := session.MakeRequest(t, req, http.StatusOK) + var fileResponse api.FileResponse + DecodeJSON(t, resp, &fileResponse) + expectedSHA := "08bd14b2e2852529157324de9c226b3364e76136" + expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/update/file%d.txt", fileID) + expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) + assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) + assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) + assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) + assert.EqualValues(t, updateFileOptions.Message+"\n", fileResponse.Commit.Message) + + // Test updating a file and renaming it + updateFileOptions = getUpdateFileOptions() + updateFileOptions.BranchName = repo1.DefaultBranch + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + updateFileOptions.FromPath = treePath + treePath = "rename/" + treePath + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &fileResponse) + expectedSHA = "08bd14b2e2852529157324de9c226b3364e76136" + expectedHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID) + expectedDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) + assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) + assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) + assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) + + // Test updating a file without a message + updateFileOptions = getUpdateFileOptions() + updateFileOptions.Message = "" + updateFileOptions.BranchName = repo1.DefaultBranch + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &fileResponse) + expectedMessage := "Update '" + treePath + "'\n" + assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) + + // Test updating a file with the wrong SHA + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + updateFileOptions = getUpdateFileOptions() + correctSHA := updateFileOptions.SHA + updateFileOptions.SHA = "badsha" + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity) + expectedAPIError := context.APIError{ + Message: "sha does not match [given: " + updateFileOptions.SHA + ", expected: " + correctSHA + "]", + URL: setting.API.SwaggerURL, + } + var apiError context.APIError + DecodeJSON(t, resp, &apiError) + assert.Equal(t, expectedAPIError, apiError) + + // Test creating a file in repo1 by user4 who does not have write access + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo16, treePath) + updateFileOptions = getUpdateFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Tests a repo with no token given so will fail + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo16, treePath) + updateFileOptions = getUpdateFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test using access token for a private repo that the user of the token owns + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo16, treePath) + updateFileOptions = getUpdateFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + session.MakeRequest(t, req, http.StatusOK) + + // Test using org repo "user3/repo3" where user2 is a collaborator + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user3, repo3, treePath) + updateFileOptions = getUpdateFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + session.MakeRequest(t, req, http.StatusOK) + + // Test using org repo "user3/repo3" with no user token + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user3, repo3, treePath) + updateFileOptions = getUpdateFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test using repo "user2/repo1" where user4 is a NOT collaborator + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + updateFileOptions = getUpdateFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + session.MakeRequest(t, req, http.StatusForbidden) + }) +} diff --git a/tests/integration/api_repo_get_contents_list_test.go b/tests/integration/api_repo_get_contents_list_test.go new file mode 100644 index 0000000000..4f2f5cb528 --- /dev/null +++ b/tests/integration/api_repo_get_contents_list_test.go @@ -0,0 +1,167 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "path/filepath" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + repo_service "code.gitea.io/gitea/services/repository" + + "github.com/stretchr/testify/assert" +) + +func getExpectedContentsListResponseForContents(ref, refType, lastCommitSHA string) []*api.ContentsResponse { + treePath := "README.md" + sha := "4b4851ad51df6a7d9f25c979345979eaeb5b349f" + selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=" + ref + htmlURL := setting.AppURL + "user2/repo1/src/" + refType + "/" + ref + "/" + treePath + gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha + downloadURL := setting.AppURL + "user2/repo1/raw/" + refType + "/" + ref + "/" + treePath + return []*api.ContentsResponse{ + { + Name: filepath.Base(treePath), + Path: treePath, + SHA: sha, + LastCommitSHA: lastCommitSHA, + Type: "file", + Size: 30, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, + Links: &api.FileLinksResponse{ + Self: &selfURL, + GitURL: &gitURL, + HTMLURL: &htmlURL, + }, + }, + } +} + +func TestAPIGetContentsList(t *testing.T) { + onGiteaRun(t, testAPIGetContentsList) +} + +func testAPIGetContentsList(t *testing.T, u *url.URL) { + /*** SETUP ***/ + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo + repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo + treePath := "" // root dir + + // Get user2's token + session := loginUser(t, user2.Name) + token2 := getTokenForLoggedInUser(t, session) + // Get user4's token + session = loginUser(t, user4.Name) + token4 := getTokenForLoggedInUser(t, session) + session = emptyTestSession(t) + + // Make a new branch in repo1 + newBranch := "test_branch" + err := repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, repo1.DefaultBranch, newBranch) + assert.NoError(t, err) + // Get the commit ID of the default branch + gitRepo, err := git.OpenRepository(git.DefaultContext, repo1.RepoPath()) + assert.NoError(t, err) + defer gitRepo.Close() + + commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch) + // Make a new tag in repo1 + newTag := "test_tag" + err = gitRepo.CreateTag(newTag, commitID) + assert.NoError(t, err) + /*** END SETUP ***/ + + // ref is default ref + ref := repo1.DefaultBranch + refType := "branch" + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + resp := session.MakeRequest(t, req, http.StatusOK) + var contentsListResponse []*api.ContentsResponse + DecodeJSON(t, resp, &contentsListResponse) + assert.NotNil(t, contentsListResponse) + lastCommit, err := gitRepo.GetCommitByPath("README.md") + assert.NoError(t, err) + expectedContentsListResponse := getExpectedContentsListResponseForContents(ref, refType, lastCommit.ID.String()) + assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) + + // No ref + refType = "branch" + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &contentsListResponse) + assert.NotNil(t, contentsListResponse) + + expectedContentsListResponse = getExpectedContentsListResponseForContents(repo1.DefaultBranch, refType, lastCommit.ID.String()) + assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) + + // ref is the branch we created above in setup + ref = newBranch + refType = "branch" + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &contentsListResponse) + assert.NotNil(t, contentsListResponse) + branchCommit, err := gitRepo.GetBranchCommit(ref) + assert.NoError(t, err) + lastCommit, err = branchCommit.GetCommitByPath("README.md") + assert.NoError(t, err) + expectedContentsListResponse = getExpectedContentsListResponseForContents(ref, refType, lastCommit.ID.String()) + assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) + + // ref is the new tag we created above in setup + ref = newTag + refType = "tag" + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &contentsListResponse) + assert.NotNil(t, contentsListResponse) + tagCommit, err := gitRepo.GetTagCommit(ref) + assert.NoError(t, err) + lastCommit, err = tagCommit.GetCommitByPath("README.md") + assert.NoError(t, err) + expectedContentsListResponse = getExpectedContentsListResponseForContents(ref, refType, lastCommit.ID.String()) + assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) + + // ref is a commit + ref = commitID + refType = "commit" + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &contentsListResponse) + assert.NotNil(t, contentsListResponse) + expectedContentsListResponse = getExpectedContentsListResponseForContents(ref, refType, commitID) + assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) + + // Test file contents a file with a bad ref + ref = "badref" + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test accessing private ref with user token that does not have access - should fail + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test access private ref of owner of token + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/readme.md?token=%s", user2.Name, repo16.Name, token2) + session.MakeRequest(t, req, http.StatusOK) + + // Test access of org user3 private repo file by owner user2 + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) + session.MakeRequest(t, req, http.StatusOK) +} diff --git a/tests/integration/api_repo_get_contents_test.go b/tests/integration/api_repo_get_contents_test.go new file mode 100644 index 0000000000..dddc316e1a --- /dev/null +++ b/tests/integration/api_repo_get_contents_test.go @@ -0,0 +1,163 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + repo_service "code.gitea.io/gitea/services/repository" + + "github.com/stretchr/testify/assert" +) + +func getExpectedContentsResponseForContents(ref, refType, lastCommitSHA string) *api.ContentsResponse { + treePath := "README.md" + sha := "4b4851ad51df6a7d9f25c979345979eaeb5b349f" + encoding := "base64" + content := "IyByZXBvMQoKRGVzY3JpcHRpb24gZm9yIHJlcG8x" + selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=" + ref + htmlURL := setting.AppURL + "user2/repo1/src/" + refType + "/" + ref + "/" + treePath + gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha + downloadURL := setting.AppURL + "user2/repo1/raw/" + refType + "/" + ref + "/" + treePath + return &api.ContentsResponse{ + Name: treePath, + Path: treePath, + SHA: sha, + LastCommitSHA: lastCommitSHA, + Type: "file", + Size: 30, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, + Links: &api.FileLinksResponse{ + Self: &selfURL, + GitURL: &gitURL, + HTMLURL: &htmlURL, + }, + } +} + +func TestAPIGetContents(t *testing.T) { + onGiteaRun(t, testAPIGetContents) +} + +func testAPIGetContents(t *testing.T, u *url.URL) { + /*** SETUP ***/ + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo + repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo + treePath := "README.md" + + // Get user2's token + session := loginUser(t, user2.Name) + token2 := getTokenForLoggedInUser(t, session) + // Get user4's token + session = loginUser(t, user4.Name) + token4 := getTokenForLoggedInUser(t, session) + session = emptyTestSession(t) + + // Make a new branch in repo1 + newBranch := "test_branch" + err := repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, repo1.DefaultBranch, newBranch) + assert.NoError(t, err) + // Get the commit ID of the default branch + gitRepo, err := git.OpenRepository(git.DefaultContext, repo1.RepoPath()) + assert.NoError(t, err) + defer gitRepo.Close() + + commitID, err := gitRepo.GetBranchCommitID(repo1.DefaultBranch) + assert.NoError(t, err) + // Make a new tag in repo1 + newTag := "test_tag" + err = gitRepo.CreateTag(newTag, commitID) + assert.NoError(t, err) + /*** END SETUP ***/ + + // ref is default ref + ref := repo1.DefaultBranch + refType := "branch" + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + resp := session.MakeRequest(t, req, http.StatusOK) + var contentsResponse api.ContentsResponse + DecodeJSON(t, resp, &contentsResponse) + assert.NotNil(t, contentsResponse) + lastCommit, _ := gitRepo.GetCommitByPath("README.md") + expectedContentsResponse := getExpectedContentsResponseForContents(ref, refType, lastCommit.ID.String()) + assert.EqualValues(t, *expectedContentsResponse, contentsResponse) + + // No ref + refType = "branch" + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &contentsResponse) + assert.NotNil(t, contentsResponse) + expectedContentsResponse = getExpectedContentsResponseForContents(repo1.DefaultBranch, refType, lastCommit.ID.String()) + assert.EqualValues(t, *expectedContentsResponse, contentsResponse) + + // ref is the branch we created above in setup + ref = newBranch + refType = "branch" + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &contentsResponse) + assert.NotNil(t, contentsResponse) + branchCommit, _ := gitRepo.GetBranchCommit(ref) + lastCommit, _ = branchCommit.GetCommitByPath("README.md") + expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType, lastCommit.ID.String()) + assert.EqualValues(t, *expectedContentsResponse, contentsResponse) + + // ref is the new tag we created above in setup + ref = newTag + refType = "tag" + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &contentsResponse) + assert.NotNil(t, contentsResponse) + tagCommit, _ := gitRepo.GetTagCommit(ref) + lastCommit, _ = tagCommit.GetCommitByPath("README.md") + expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType, lastCommit.ID.String()) + assert.EqualValues(t, *expectedContentsResponse, contentsResponse) + + // ref is a commit + ref = commitID + refType = "commit" + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &contentsResponse) + assert.NotNil(t, contentsResponse) + expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType, commitID) + assert.EqualValues(t, *expectedContentsResponse, contentsResponse) + + // Test file contents a file with a bad ref + ref = "badref" + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test accessing private ref with user token that does not have access - should fail + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test access private ref of owner of token + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/readme.md?token=%s", user2.Name, repo16.Name, token2) + session.MakeRequest(t, req, http.StatusOK) + + // Test access of org user3 private repo file by owner user2 + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) + session.MakeRequest(t, req, http.StatusOK) +} diff --git a/tests/integration/api_repo_git_blobs_test.go b/tests/integration/api_repo_git_blobs_test.go new file mode 100644 index 0000000000..cb5116c743 --- /dev/null +++ b/tests/integration/api_repo_git_blobs_test.go @@ -0,0 +1,79 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIReposGitBlobs(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3 + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo + repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo + repo1ReadmeSHA := "65f1bf27bc3bf70f64657658635e66094edbcb4d" + repo3ReadmeSHA := "d56a3073c1dbb7b15963110a049d50cdb5db99fc" + repo16ReadmeSHA := "f90451c72ef61a7645293d17b47be7a8e983da57" + badSHA := "0000000000000000000000000000000000000000" + + // Login as User2. + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session) + session = emptyTestSession(t) // don't want anyone logged in for this + + // Test a public repo that anyone can GET the blob of + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", user2.Name, repo1.Name, repo1ReadmeSHA) + resp := session.MakeRequest(t, req, http.StatusOK) + var gitBlobResponse api.GitBlobResponse + DecodeJSON(t, resp, &gitBlobResponse) + assert.NotNil(t, gitBlobResponse) + expectedContent := "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK" + assert.Equal(t, expectedContent, gitBlobResponse.Content) + + // Tests a private repo with no token so will fail + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", user2.Name, repo16.Name, repo16ReadmeSHA) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test using access token for a private repo that the user of the token owns + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s?token=%s", user2.Name, repo16.Name, repo16ReadmeSHA, token) + session.MakeRequest(t, req, http.StatusOK) + + // Test using bad sha + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", user2.Name, repo1.Name, badSHA) + session.MakeRequest(t, req, http.StatusBadRequest) + + // Test using org repo "user3/repo3" where user2 is a collaborator + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s?token=%s", user3.Name, repo3.Name, repo3ReadmeSHA, token) + session.MakeRequest(t, req, http.StatusOK) + + // Test using org repo "user3/repo3" where user2 is a collaborator + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s?token=%s", user3.Name, repo3.Name, repo3ReadmeSHA, token) + session.MakeRequest(t, req, http.StatusOK) + + // Test using org repo "user3/repo3" with no user token + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", user3.Name, repo3ReadmeSHA, repo3.Name) + session.MakeRequest(t, req, http.StatusNotFound) + + // Login as User4. + session = loginUser(t, user4.Name) + token4 := getTokenForLoggedInUser(t, session) + session = emptyTestSession(t) // don't want anyone logged in for this + + // Test using org repo "user3/repo3" where user4 is a NOT collaborator + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/d56a3073c1dbb7b15963110a049d50cdb5db99fc?access=%s", user3.Name, repo3.Name, token4) + session.MakeRequest(t, req, http.StatusNotFound) +} diff --git a/tests/integration/api_repo_git_commits_test.go b/tests/integration/api_repo_git_commits_test.go new file mode 100644 index 0000000000..99f83f943c --- /dev/null +++ b/tests/integration/api_repo_git_commits_test.go @@ -0,0 +1,152 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func compareCommitFiles(t *testing.T, expect []string, files []*api.CommitAffectedFiles) { + var actual []string + for i := range files { + actual = append(actual, files[i].Filename) + } + assert.ElementsMatch(t, expect, actual) +} + +func TestAPIReposGitCommits(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + // check invalid requests + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/commits/12345?token="+token, user.Name) + session.MakeRequest(t, req, http.StatusNotFound) + + req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/commits/..?token="+token, user.Name) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/commits/branch-not-exist?token="+token, user.Name) + session.MakeRequest(t, req, http.StatusNotFound) + + for _, ref := range [...]string{ + "master", // Branch + "v1.1", // Tag + "65f1", // short sha + "65f1bf27bc3bf70f64657658635e66094edbcb4d", // full sha + } { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/commits/%s?token="+token, user.Name, ref) + session.MakeRequest(t, req, http.StatusOK) + } +} + +func TestAPIReposGitCommitList(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + // Test getting commits (Page 1) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?token="+token, user.Name) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiData []api.Commit + DecodeJSON(t, resp, &apiData) + + assert.Len(t, apiData, 3) + assert.EqualValues(t, "69554a64c1e6030f051e5c3f94bfbd773cd6a324", apiData[0].CommitMeta.SHA) + compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files) + assert.EqualValues(t, "27566bd5738fc8b4e3fef3c5e72cce608537bd95", apiData[1].CommitMeta.SHA) + compareCommitFiles(t, []string{"readme.md"}, apiData[1].Files) + assert.EqualValues(t, "5099b81332712fe655e34e8dd63574f503f61811", apiData[2].CommitMeta.SHA) + compareCommitFiles(t, []string{"readme.md"}, apiData[2].Files) +} + +func TestAPIReposGitCommitListPage2Empty(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + // Test getting commits (Page=2) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?token="+token+"&page=2", user.Name) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiData []api.Commit + DecodeJSON(t, resp, &apiData) + + assert.Len(t, apiData, 0) +} + +func TestAPIReposGitCommitListDifferentBranch(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + // Test getting commits (Page=1, Branch=good-sign) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?token="+token+"&sha=good-sign", user.Name) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiData []api.Commit + DecodeJSON(t, resp, &apiData) + + assert.Len(t, apiData, 1) + assert.Equal(t, "f27c2b2b03dcab38beaf89b0ab4ff61f6de63441", apiData[0].CommitMeta.SHA) + compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files) +} + +func TestDownloadCommitDiffOrPatch(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + // Test getting diff + reqDiff := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/git/commits/f27c2b2b03dcab38beaf89b0ab4ff61f6de63441.diff?token="+token, user.Name) + resp := session.MakeRequest(t, reqDiff, http.StatusOK) + assert.EqualValues(t, + "commit f27c2b2b03dcab38beaf89b0ab4ff61f6de63441\nAuthor: User2 <user2@example.com>\nDate: Sun Aug 6 19:55:01 2017 +0200\n\n good signed commit\n\ndiff --git a/readme.md b/readme.md\nnew file mode 100644\nindex 0000000..458121c\n--- /dev/null\n+++ b/readme.md\n@@ -0,0 +1 @@\n+good sign\n", + resp.Body.String()) + + // Test getting patch + reqPatch := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/git/commits/f27c2b2b03dcab38beaf89b0ab4ff61f6de63441.patch?token="+token, user.Name) + resp = session.MakeRequest(t, reqPatch, http.StatusOK) + assert.EqualValues(t, + "From f27c2b2b03dcab38beaf89b0ab4ff61f6de63441 Mon Sep 17 00:00:00 2001\nFrom: User2 <user2@example.com>\nDate: Sun, 6 Aug 2017 19:55:01 +0200\nSubject: [PATCH] good signed commit\n\n---\n readme.md | 1 +\n 1 file changed, 1 insertion(+)\n create mode 100644 readme.md\n\ndiff --git a/readme.md b/readme.md\nnew file mode 100644\nindex 0000000..458121c\n--- /dev/null\n+++ b/readme.md\n@@ -0,0 +1 @@\n+good sign\n", + resp.Body.String()) +} + +func TestGetFileHistory(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?path=readme.md&token="+token+"&sha=good-sign", user.Name) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiData []api.Commit + DecodeJSON(t, resp, &apiData) + + assert.Len(t, apiData, 1) + assert.Equal(t, "f27c2b2b03dcab38beaf89b0ab4ff61f6de63441", apiData[0].CommitMeta.SHA) + compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files) +} diff --git a/tests/integration/api_repo_git_hook_test.go b/tests/integration/api_repo_git_hook_test.go new file mode 100644 index 0000000000..a6c4f91d4a --- /dev/null +++ b/tests/integration/api_repo_git_hook_test.go @@ -0,0 +1,197 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +const testHookContent = `#!/bin/bash + +echo Hello, World! +` + +func TestAPIListGitHooks(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 37}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + // user1 is an admin user + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git?token=%s", + owner.Name, repo.Name, token) + resp := MakeRequest(t, req, http.StatusOK) + var apiGitHooks []*api.GitHook + DecodeJSON(t, resp, &apiGitHooks) + assert.Len(t, apiGitHooks, 3) + for _, apiGitHook := range apiGitHooks { + if apiGitHook.Name == "pre-receive" { + assert.True(t, apiGitHook.IsActive) + assert.Equal(t, testHookContent, apiGitHook.Content) + } else { + assert.False(t, apiGitHook.IsActive) + assert.Empty(t, apiGitHook.Content) + } + } +} + +func TestAPIListGitHooksNoHooks(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + // user1 is an admin user + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git?token=%s", + owner.Name, repo.Name, token) + resp := MakeRequest(t, req, http.StatusOK) + var apiGitHooks []*api.GitHook + DecodeJSON(t, resp, &apiGitHooks) + assert.Len(t, apiGitHooks, 3) + for _, apiGitHook := range apiGitHooks { + assert.False(t, apiGitHook.IsActive) + assert.Empty(t, apiGitHook.Content) + } +} + +func TestAPIListGitHooksNoAccess(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git?token=%s", + owner.Name, repo.Name, token) + MakeRequest(t, req, http.StatusForbidden) +} + +func TestAPIGetGitHook(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 37}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + // user1 is an admin user + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s", + owner.Name, repo.Name, token) + resp := MakeRequest(t, req, http.StatusOK) + var apiGitHook *api.GitHook + DecodeJSON(t, resp, &apiGitHook) + assert.True(t, apiGitHook.IsActive) + assert.Equal(t, testHookContent, apiGitHook.Content) +} + +func TestAPIGetGitHookNoAccess(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s", + owner.Name, repo.Name, token) + MakeRequest(t, req, http.StatusForbidden) +} + +func TestAPIEditGitHook(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + // user1 is an admin user + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session) + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s", + owner.Name, repo.Name, token) + req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditGitHookOption{ + Content: testHookContent, + }) + resp := MakeRequest(t, req, http.StatusOK) + var apiGitHook *api.GitHook + DecodeJSON(t, resp, &apiGitHook) + assert.True(t, apiGitHook.IsActive) + assert.Equal(t, testHookContent, apiGitHook.Content) + + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s", + owner.Name, repo.Name, token) + resp = MakeRequest(t, req, http.StatusOK) + var apiGitHook2 *api.GitHook + DecodeJSON(t, resp, &apiGitHook2) + assert.True(t, apiGitHook2.IsActive) + assert.Equal(t, testHookContent, apiGitHook2.Content) +} + +func TestAPIEditGitHookNoAccess(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s", + owner.Name, repo.Name, token) + req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditGitHookOption{ + Content: testHookContent, + }) + MakeRequest(t, req, http.StatusForbidden) +} + +func TestAPIDeleteGitHook(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 37}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + // user1 is an admin user + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session) + + req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s", + owner.Name, repo.Name, token) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s", + owner.Name, repo.Name, token) + resp := MakeRequest(t, req, http.StatusOK) + var apiGitHook2 *api.GitHook + DecodeJSON(t, resp, &apiGitHook2) + assert.False(t, apiGitHook2.IsActive) + assert.Empty(t, apiGitHook2.Content) +} + +func TestAPIDeleteGitHookNoAccess(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s", + owner.Name, repo.Name, token) + MakeRequest(t, req, http.StatusForbidden) +} diff --git a/tests/integration/api_repo_git_notes_test.go b/tests/integration/api_repo_git_notes_test.go new file mode 100644 index 0000000000..713c7599c3 --- /dev/null +++ b/tests/integration/api_repo_git_notes_test.go @@ -0,0 +1,41 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func TestAPIReposGitNotes(t *testing.T) { + onGiteaRun(t, func(*testing.T, *url.URL) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + // check invalid requests + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/12345?token=%s", user.Name, token) + session.MakeRequest(t, req, http.StatusNotFound) + + req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/..?token=%s", user.Name, token) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + // check valid request + req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/65f1bf27bc3bf70f64657658635e66094edbcb4d?token=%s", user.Name, token) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiData api.Note + DecodeJSON(t, resp, &apiData) + assert.Equal(t, "This is a test note\n", apiData.Message) + }) +} diff --git a/tests/integration/api_repo_git_ref_test.go b/tests/integration/api_repo_git_ref_test.go new file mode 100644 index 0000000000..e8fc47f8dc --- /dev/null +++ b/tests/integration/api_repo_git_ref_test.go @@ -0,0 +1,36 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/tests" +) + +func TestAPIReposGitRefs(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + for _, ref := range [...]string{ + "refs/heads/master", // Branch + "refs/tags/v1.1", // Tag + } { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/%s?token="+token, user.Name, ref) + session.MakeRequest(t, req, http.StatusOK) + } + // Test getting all refs + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/refs?token="+token, user.Name) + session.MakeRequest(t, req, http.StatusOK) + // Test getting non-existent refs + req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/refs/heads/unknown?token="+token, user.Name) + session.MakeRequest(t, req, http.StatusNotFound) +} diff --git a/tests/integration/api_repo_git_tags_test.go b/tests/integration/api_repo_git_tags_test.go new file mode 100644 index 0000000000..855eb2451e --- /dev/null +++ b/tests/integration/api_repo_git_tags_test.go @@ -0,0 +1,88 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIGitTags(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + // Set up git config for the tagger + _ = git.NewCommand(git.DefaultContext, "config", "user.name", user.Name).Run(&git.RunOpts{Dir: repo.RepoPath()}) + _ = git.NewCommand(git.DefaultContext, "config", "user.email", user.Email).Run(&git.RunOpts{Dir: repo.RepoPath()}) + + gitRepo, _ := git.OpenRepository(git.DefaultContext, repo.RepoPath()) + defer gitRepo.Close() + + commit, _ := gitRepo.GetBranchCommit("master") + lTagName := "lightweightTag" + gitRepo.CreateTag(lTagName, commit.ID.String()) + + aTagName := "annotatedTag" + aTagMessage := "my annotated message" + gitRepo.CreateAnnotatedTag(aTagName, aTagMessage, commit.ID.String()) + aTag, _ := gitRepo.GetTag(aTagName) + + // SHOULD work for annotated tags + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s?token=%s", user.Name, repo.Name, aTag.ID.String(), token) + res := session.MakeRequest(t, req, http.StatusOK) + + var tag *api.AnnotatedTag + DecodeJSON(t, res, &tag) + + assert.Equal(t, aTagName, tag.Tag) + assert.Equal(t, aTag.ID.String(), tag.SHA) + assert.Equal(t, commit.ID.String(), tag.Object.SHA) + assert.Equal(t, aTagMessage+"\n", tag.Message) + assert.Equal(t, user.Name, tag.Tagger.Name) + assert.Equal(t, user.Email, tag.Tagger.Email) + assert.Equal(t, util.URLJoin(repo.APIURL(), "git/tags", aTag.ID.String()), tag.URL) + + // Should NOT work for lightweight tags + badReq := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s?token=%s", user.Name, repo.Name, commit.ID.String(), token) + session.MakeRequest(t, badReq, http.StatusBadRequest) +} + +func TestAPIDeleteTagByName(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, owner.LowerName) + token := getTokenForLoggedInUser(t, session) + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/tags/delete-tag?token=%s", + owner.Name, repo.Name, token) + + req := NewRequestf(t, http.MethodDelete, urlStr) + _ = session.MakeRequest(t, req, http.StatusNoContent) + + // Make sure that actual releases can't be deleted outright + createNewReleaseUsingAPI(t, session, token, owner, repo, "release-tag", "", "Release Tag", "test") + urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/tags/release-tag?token=%s", + owner.Name, repo.Name, token) + + req = NewRequestf(t, http.MethodDelete, urlStr) + _ = session.MakeRequest(t, req, http.StatusConflict) +} diff --git a/tests/integration/api_repo_git_trees_test.go b/tests/integration/api_repo_git_trees_test.go new file mode 100644 index 0000000000..385fec12ba --- /dev/null +++ b/tests/integration/api_repo_git_trees_test.go @@ -0,0 +1,77 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/tests" +) + +func TestAPIReposGitTrees(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3 + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo + repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo + repo1TreeSHA := "65f1bf27bc3bf70f64657658635e66094edbcb4d" + repo3TreeSHA := "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6" + repo16TreeSHA := "69554a64c1e6030f051e5c3f94bfbd773cd6a324" + badSHA := "0000000000000000000000000000000000000000" + + // Login as User2. + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session) + session = emptyTestSession(t) // don't want anyone logged in for this + + // Test a public repo that anyone can GET the tree of + for _, ref := range [...]string{ + "master", // Branch + repo1TreeSHA, // Tree SHA + } { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/%s", user2.Name, repo1.Name, ref) + session.MakeRequest(t, req, http.StatusOK) + } + + // Tests a private repo with no token so will fail + for _, ref := range [...]string{ + "master", // Branch + repo1TreeSHA, // Tag + } { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/%s", user2.Name, repo16.Name, ref) + session.MakeRequest(t, req, http.StatusNotFound) + } + + // Test using access token for a private repo that the user of the token owns + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/%s?token=%s", user2.Name, repo16.Name, repo16TreeSHA, token) + session.MakeRequest(t, req, http.StatusOK) + + // Test using bad sha + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/%s", user2.Name, repo1.Name, badSHA) + session.MakeRequest(t, req, http.StatusBadRequest) + + // Test using org repo "user3/repo3" where user2 is a collaborator + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/%s?token=%s", user3.Name, repo3.Name, repo3TreeSHA, token) + session.MakeRequest(t, req, http.StatusOK) + + // Test using org repo "user3/repo3" with no user token + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/%s", user3.Name, repo3TreeSHA, repo3.Name) + session.MakeRequest(t, req, http.StatusNotFound) + + // Login as User4. + session = loginUser(t, user4.Name) + token4 := getTokenForLoggedInUser(t, session) + session = emptyTestSession(t) // don't want anyone logged in for this + + // Test using org repo "user3/repo3" where user4 is a NOT collaborator + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/d56a3073c1dbb7b15963110a049d50cdb5db99fc?access=%s", user3.Name, repo3.Name, token4) + session.MakeRequest(t, req, http.StatusNotFound) +} diff --git a/tests/integration/api_repo_languages_test.go b/tests/integration/api_repo_languages_test.go new file mode 100644 index 0000000000..98373fb6b1 --- /dev/null +++ b/tests/integration/api_repo_languages_test.go @@ -0,0 +1,50 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestRepoLanguages(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user2") + + // Request editor page + req := NewRequest(t, "GET", "/user2/repo1/_new/master/") + resp := session.MakeRequest(t, req, http.StatusOK) + + doc := NewHTMLParser(t, resp.Body) + lastCommit := doc.GetInputValueByName("last_commit") + assert.NotEmpty(t, lastCommit) + + // Save new file to master branch + req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{ + "_csrf": doc.GetCSRF(), + "last_commit": lastCommit, + "tree_path": "test.go", + "content": "package main", + "commit_choice": "direct", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // let gitea calculate language stats + time.Sleep(time.Second) + + // Save new file to master branch + req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/languages") + resp = session.MakeRequest(t, req, http.StatusOK) + + var languages map[string]int64 + DecodeJSON(t, resp, &languages) + + assert.InDeltaMapValues(t, map[string]int64{"Go": 12}, languages, 0) + }) +} diff --git a/tests/integration/api_repo_lfs_locks_test.go b/tests/integration/api_repo_lfs_locks_test.go new file mode 100644 index 0000000000..0860f47533 --- /dev/null +++ b/tests/integration/api_repo_lfs_locks_test.go @@ -0,0 +1,181 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + "time" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/lfs" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPILFSLocksNotStarted(t *testing.T) { + defer tests.PrepareTestEnv(t)() + setting.LFS.StartServer = false + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + req := NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks/verify", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks/10/unlock", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) +} + +func TestAPILFSLocksNotLogin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + setting.LFS.StartServer = true + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + req := NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) + req.Header.Set("Accept", lfs.MediaType) + resp := MakeRequest(t, req, http.StatusUnauthorized) + var lfsLockError api.LFSLockError + DecodeJSON(t, resp, &lfsLockError) + assert.Equal(t, "You must have pull access to list locks", lfsLockError.Message) +} + +func TestAPILFSLocksLogged(t *testing.T) { + defer tests.PrepareTestEnv(t)() + setting.LFS.StartServer = true + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // in org 3 + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // in org 3 + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // own by org 3 + + tests := []struct { + user *user_model.User + repo *repo_model.Repository + path string + httpResult int + addTime []int + }{ + {user: user2, repo: repo1, path: "foo/bar.zip", httpResult: http.StatusCreated, addTime: []int{0}}, + {user: user2, repo: repo1, path: "path/test", httpResult: http.StatusCreated, addTime: []int{0}}, + {user: user2, repo: repo1, path: "path/test", httpResult: http.StatusConflict}, + {user: user2, repo: repo1, path: "Foo/BaR.zip", httpResult: http.StatusConflict}, + {user: user2, repo: repo1, path: "Foo/Test/../subFOlder/../Relative/../BaR.zip", httpResult: http.StatusConflict}, + {user: user4, repo: repo1, path: "FoO/BaR.zip", httpResult: http.StatusUnauthorized}, + {user: user4, repo: repo1, path: "path/test-user4", httpResult: http.StatusUnauthorized}, + {user: user2, repo: repo1, path: "patH/Test-user4", httpResult: http.StatusCreated, addTime: []int{0}}, + {user: user2, repo: repo1, path: "some/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/path", httpResult: http.StatusCreated, addTime: []int{0}}, + + {user: user2, repo: repo3, path: "test/foo/bar.zip", httpResult: http.StatusCreated, addTime: []int{1, 2}}, + {user: user4, repo: repo3, path: "test/foo/bar.zip", httpResult: http.StatusConflict}, + {user: user4, repo: repo3, path: "test/foo/bar.bin", httpResult: http.StatusCreated, addTime: []int{1, 2}}, + } + + resultsTests := []struct { + user *user_model.User + repo *repo_model.Repository + totalCount int + oursCount int + theirsCount int + locksOwners []*user_model.User + locksTimes []time.Time + }{ + {user: user2, repo: repo1, totalCount: 4, oursCount: 4, theirsCount: 0, locksOwners: []*user_model.User{user2, user2, user2, user2}, locksTimes: []time.Time{}}, + {user: user2, repo: repo3, totalCount: 2, oursCount: 1, theirsCount: 1, locksOwners: []*user_model.User{user2, user4}, locksTimes: []time.Time{}}, + {user: user4, repo: repo3, totalCount: 2, oursCount: 1, theirsCount: 1, locksOwners: []*user_model.User{user2, user4}, locksTimes: []time.Time{}}, + } + + deleteTests := []struct { + user *user_model.User + repo *repo_model.Repository + lockID string + }{} + + // create locks + for _, test := range tests { + session := loginUser(t, test.user.Name) + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks", test.repo.FullName()), map[string]string{"path": test.path}) + req.Header.Set("Accept", lfs.MediaType) + req.Header.Set("Content-Type", lfs.MediaType) + resp := session.MakeRequest(t, req, test.httpResult) + if len(test.addTime) > 0 { + var lfsLock api.LFSLockResponse + DecodeJSON(t, resp, &lfsLock) + assert.EqualValues(t, lfsLock.Lock.LockedAt.Format(time.RFC3339), lfsLock.Lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second + for _, id := range test.addTime { + resultsTests[id].locksTimes = append(resultsTests[id].locksTimes, time.Now()) + } + } + } + + // check creation + for _, test := range resultsTests { + session := loginUser(t, test.user.Name) + req := NewRequestf(t, "GET", "/%s.git/info/lfs/locks", test.repo.FullName()) + req.Header.Set("Accept", lfs.MediaType) + resp := session.MakeRequest(t, req, http.StatusOK) + var lfsLocks api.LFSLockList + DecodeJSON(t, resp, &lfsLocks) + assert.Len(t, lfsLocks.Locks, test.totalCount) + for i, lock := range lfsLocks.Locks { + assert.EqualValues(t, test.locksOwners[i].DisplayName(), lock.Owner.Name) + assert.WithinDuration(t, test.locksTimes[i], lock.LockedAt, 10*time.Second) + assert.EqualValues(t, lock.LockedAt.Format(time.RFC3339), lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second + } + + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/verify", test.repo.FullName()), map[string]string{}) + req.Header.Set("Accept", lfs.MediaType) + req.Header.Set("Content-Type", lfs.MediaType) + resp = session.MakeRequest(t, req, http.StatusOK) + var lfsLocksVerify api.LFSLockListVerify + DecodeJSON(t, resp, &lfsLocksVerify) + assert.Len(t, lfsLocksVerify.Ours, test.oursCount) + assert.Len(t, lfsLocksVerify.Theirs, test.theirsCount) + for _, lock := range lfsLocksVerify.Ours { + assert.EqualValues(t, test.user.DisplayName(), lock.Owner.Name) + deleteTests = append(deleteTests, struct { + user *user_model.User + repo *repo_model.Repository + lockID string + }{test.user, test.repo, lock.ID}) + } + for _, lock := range lfsLocksVerify.Theirs { + assert.NotEqual(t, test.user.DisplayName(), lock.Owner.Name) + } + } + + // remove all locks + for _, test := range deleteTests { + session := loginUser(t, test.user.Name) + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/%s/unlock", test.repo.FullName(), test.lockID), map[string]string{}) + req.Header.Set("Accept", lfs.MediaType) + req.Header.Set("Content-Type", lfs.MediaType) + resp := session.MakeRequest(t, req, http.StatusOK) + var lfsLockRep api.LFSLockResponse + DecodeJSON(t, resp, &lfsLockRep) + assert.Equal(t, test.lockID, lfsLockRep.Lock.ID) + assert.Equal(t, test.user.DisplayName(), lfsLockRep.Lock.Owner.Name) + } + + // check that we don't have any lock + for _, test := range resultsTests { + session := loginUser(t, test.user.Name) + req := NewRequestf(t, "GET", "/%s.git/info/lfs/locks", test.repo.FullName()) + req.Header.Set("Accept", lfs.MediaType) + resp := session.MakeRequest(t, req, http.StatusOK) + var lfsLocks api.LFSLockList + DecodeJSON(t, resp, &lfsLocks) + assert.Len(t, lfsLocks.Locks, 0) + } +} diff --git a/tests/integration/api_repo_lfs_migrate_test.go b/tests/integration/api_repo_lfs_migrate_test.go new file mode 100644 index 0000000000..d2edf67e8b --- /dev/null +++ b/tests/integration/api_repo_lfs_migrate_test.go @@ -0,0 +1,54 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "path" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/lfs" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/services/migrations" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIRepoLFSMigrateLocal(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + oldImportLocalPaths := setting.ImportLocalPaths + oldAllowLocalNetworks := setting.Migrations.AllowLocalNetworks + setting.ImportLocalPaths = true + setting.Migrations.AllowLocalNetworks = true + assert.NoError(t, migrations.Init()) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate?token="+token, &api.MigrateRepoOptions{ + CloneAddr: path.Join(setting.RepoRootPath, "migration/lfs-test.git"), + RepoOwnerID: user.ID, + RepoName: "lfs-test-local", + LFS: true, + }) + resp := MakeRequest(t, req, NoExpectedStatus) + assert.EqualValues(t, http.StatusCreated, resp.Code) + + store := lfs.NewContentStore() + ok, _ := store.Verify(lfs.Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041", Size: 6}) + assert.True(t, ok) + ok, _ = store.Verify(lfs.Pointer{Oid: "d6f175817f886ec6fbbc1515326465fa96c3bfd54a4ea06cfd6dbbd8340e0152", Size: 6}) + assert.True(t, ok) + + setting.ImportLocalPaths = oldImportLocalPaths + setting.Migrations.AllowLocalNetworks = oldAllowLocalNetworks + assert.NoError(t, migrations.Init()) // reset old migration settings +} diff --git a/tests/integration/api_repo_lfs_test.go b/tests/integration/api_repo_lfs_test.go new file mode 100644 index 0000000000..440dd04a81 --- /dev/null +++ b/tests/integration/api_repo_lfs_test.go @@ -0,0 +1,487 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "bytes" + "net/http" + "path" + "strconv" + "strings" + "testing" + + git_model "code.gitea.io/gitea/models/git" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/lfs" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPILFSNotStarted(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + setting.LFS.StartServer = false + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + req := NewRequestf(t, "POST", "/%s/%s.git/info/lfs/objects/batch", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "PUT", "/%s/%s.git/info/lfs/objects/oid/10", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/objects/oid/name", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/objects/oid", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/verify", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) +} + +func TestAPILFSMediaType(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + setting.LFS.StartServer = true + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + req := NewRequestf(t, "POST", "/%s/%s.git/info/lfs/objects/batch", user.Name, repo.Name) + MakeRequest(t, req, http.StatusUnsupportedMediaType) + req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/verify", user.Name, repo.Name) + MakeRequest(t, req, http.StatusUnsupportedMediaType) +} + +func createLFSTestRepository(t *testing.T, name string) *repo_model.Repository { + ctx := NewAPITestContext(t, "user2", "lfs-"+name+"-repo") + t.Run("CreateRepo", doAPICreateRepository(ctx, false)) + + repo, err := repo_model.GetRepositoryByOwnerAndName("user2", "lfs-"+name+"-repo") + assert.NoError(t, err) + + return repo +} + +func TestAPILFSBatch(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + setting.LFS.StartServer = true + + repo := createLFSTestRepository(t, "batch") + + content := []byte("dummy1") + oid := storeObjectInRepo(t, repo.ID, &content) + defer git_model.RemoveLFSMetaObjectByOid(repo.ID, oid) + + session := loginUser(t, "user2") + + newRequest := func(t testing.TB, br *lfs.BatchRequest) *http.Request { + req := NewRequestWithJSON(t, "POST", "/user2/lfs-batch-repo.git/info/lfs/objects/batch", br) + req.Header.Set("Accept", lfs.MediaType) + req.Header.Set("Content-Type", lfs.MediaType) + return req + } + decodeResponse := func(t *testing.T, b *bytes.Buffer) *lfs.BatchResponse { + var br lfs.BatchResponse + + assert.NoError(t, json.Unmarshal(b.Bytes(), &br)) + return &br + } + + t.Run("InvalidJsonRequest", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, nil) + + session.MakeRequest(t, req, http.StatusBadRequest) + }) + + t.Run("InvalidOperation", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, &lfs.BatchRequest{ + Operation: "dummy", + }) + + session.MakeRequest(t, req, http.StatusBadRequest) + }) + + t.Run("InvalidPointer", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, &lfs.BatchRequest{ + Operation: "download", + Objects: []lfs.Pointer{ + {Oid: "dummy"}, + {Oid: oid, Size: -1}, + }, + }) + + resp := session.MakeRequest(t, req, http.StatusOK) + br := decodeResponse(t, resp.Body) + assert.Len(t, br.Objects, 2) + assert.Equal(t, "dummy", br.Objects[0].Oid) + assert.Equal(t, oid, br.Objects[1].Oid) + assert.Equal(t, int64(0), br.Objects[0].Size) + assert.Equal(t, int64(-1), br.Objects[1].Size) + assert.NotNil(t, br.Objects[0].Error) + assert.NotNil(t, br.Objects[1].Error) + assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) + assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[1].Error.Code) + assert.Equal(t, "Oid or size are invalid", br.Objects[0].Error.Message) + assert.Equal(t, "Oid or size are invalid", br.Objects[1].Error.Message) + }) + + t.Run("PointerSizeMismatch", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, &lfs.BatchRequest{ + Operation: "download", + Objects: []lfs.Pointer{ + {Oid: oid, Size: 1}, + }, + }) + + resp := session.MakeRequest(t, req, http.StatusOK) + br := decodeResponse(t, resp.Body) + assert.Len(t, br.Objects, 1) + assert.NotNil(t, br.Objects[0].Error) + assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) + assert.Equal(t, "Object "+oid+" is not 1 bytes", br.Objects[0].Error.Message) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("PointerNotInStore", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, &lfs.BatchRequest{ + Operation: "download", + Objects: []lfs.Pointer{ + {Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6}, + }, + }) + + resp := session.MakeRequest(t, req, http.StatusOK) + br := decodeResponse(t, resp.Body) + assert.Len(t, br.Objects, 1) + assert.NotNil(t, br.Objects[0].Error) + assert.Equal(t, http.StatusNotFound, br.Objects[0].Error.Code) + }) + + t.Run("MetaNotFound", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + p := lfs.Pointer{Oid: "05eeb4eb5be71f2dd291ca39157d6d9effd7d1ea19cbdc8a99411fe2a8f26a00", Size: 6} + + contentStore := lfs.NewContentStore() + exist, err := contentStore.Exists(p) + assert.NoError(t, err) + assert.False(t, exist) + err = contentStore.Put(p, bytes.NewReader([]byte("dummy0"))) + assert.NoError(t, err) + + req := newRequest(t, &lfs.BatchRequest{ + Operation: "download", + Objects: []lfs.Pointer{p}, + }) + + resp := session.MakeRequest(t, req, http.StatusOK) + br := decodeResponse(t, resp.Body) + assert.Len(t, br.Objects, 1) + assert.NotNil(t, br.Objects[0].Error) + assert.Equal(t, http.StatusNotFound, br.Objects[0].Error.Code) + }) + + t.Run("Success", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, &lfs.BatchRequest{ + Operation: "download", + Objects: []lfs.Pointer{ + {Oid: oid, Size: 6}, + }, + }) + + resp := session.MakeRequest(t, req, http.StatusOK) + br := decodeResponse(t, resp.Body) + assert.Len(t, br.Objects, 1) + assert.Nil(t, br.Objects[0].Error) + assert.Contains(t, br.Objects[0].Actions, "download") + l := br.Objects[0].Actions["download"] + assert.NotNil(t, l) + assert.NotEmpty(t, l.Href) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("FileTooBig", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + oldMaxFileSize := setting.LFS.MaxFileSize + setting.LFS.MaxFileSize = 2 + + req := newRequest(t, &lfs.BatchRequest{ + Operation: "upload", + Objects: []lfs.Pointer{ + {Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6}, + }, + }) + + resp := session.MakeRequest(t, req, http.StatusOK) + br := decodeResponse(t, resp.Body) + assert.Len(t, br.Objects, 1) + assert.NotNil(t, br.Objects[0].Error) + assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) + assert.Equal(t, "Size must be less than or equal to 2", br.Objects[0].Error.Message) + + setting.LFS.MaxFileSize = oldMaxFileSize + }) + + t.Run("AddMeta", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + p := lfs.Pointer{Oid: "05eeb4eb5be71f2dd291ca39157d6d9effd7d1ea19cbdc8a99411fe2a8f26a00", Size: 6} + + contentStore := lfs.NewContentStore() + exist, err := contentStore.Exists(p) + assert.NoError(t, err) + assert.True(t, exist) + + repo2 := createLFSTestRepository(t, "batch2") + content := []byte("dummy0") + storeObjectInRepo(t, repo2.ID, &content) + + meta, err := git_model.GetLFSMetaObjectByOid(repo.ID, p.Oid) + assert.Nil(t, meta) + assert.Equal(t, git_model.ErrLFSObjectNotExist, err) + + req := newRequest(t, &lfs.BatchRequest{ + Operation: "upload", + Objects: []lfs.Pointer{p}, + }) + + resp := session.MakeRequest(t, req, http.StatusOK) + br := decodeResponse(t, resp.Body) + assert.Len(t, br.Objects, 1) + assert.Nil(t, br.Objects[0].Error) + assert.Empty(t, br.Objects[0].Actions) + + meta, err = git_model.GetLFSMetaObjectByOid(repo.ID, p.Oid) + assert.NoError(t, err) + assert.NotNil(t, meta) + + // Cleanup + err = contentStore.Delete(p.RelativePath()) + assert.NoError(t, err) + }) + + t.Run("AlreadyExists", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, &lfs.BatchRequest{ + Operation: "upload", + Objects: []lfs.Pointer{ + {Oid: oid, Size: 6}, + }, + }) + + resp := session.MakeRequest(t, req, http.StatusOK) + br := decodeResponse(t, resp.Body) + assert.Len(t, br.Objects, 1) + assert.Nil(t, br.Objects[0].Error) + assert.Empty(t, br.Objects[0].Actions) + }) + + t.Run("NewFile", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, &lfs.BatchRequest{ + Operation: "upload", + Objects: []lfs.Pointer{ + {Oid: "d6f175817f886ec6fbbc1515326465fa96c3bfd54a4ea06cfd6dbbd8340e0153", Size: 1}, + }, + }) + + resp := session.MakeRequest(t, req, http.StatusOK) + br := decodeResponse(t, resp.Body) + assert.Len(t, br.Objects, 1) + assert.Nil(t, br.Objects[0].Error) + assert.Contains(t, br.Objects[0].Actions, "upload") + ul := br.Objects[0].Actions["upload"] + assert.NotNil(t, ul) + assert.NotEmpty(t, ul.Href) + assert.Contains(t, br.Objects[0].Actions, "verify") + vl := br.Objects[0].Actions["verify"] + assert.NotNil(t, vl) + assert.NotEmpty(t, vl.Href) + }) + }) +} + +func TestAPILFSUpload(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + setting.LFS.StartServer = true + + repo := createLFSTestRepository(t, "upload") + + content := []byte("dummy3") + oid := storeObjectInRepo(t, repo.ID, &content) + defer git_model.RemoveLFSMetaObjectByOid(repo.ID, oid) + + session := loginUser(t, "user2") + + newRequest := func(t testing.TB, p lfs.Pointer, content string) *http.Request { + req := NewRequestWithBody(t, "PUT", path.Join("/user2/lfs-upload-repo.git/info/lfs/objects/", p.Oid, strconv.FormatInt(p.Size, 10)), strings.NewReader(content)) + return req + } + + t.Run("InvalidPointer", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, lfs.Pointer{Oid: "dummy"}, "") + + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + }) + + t.Run("AlreadyExistsInStore", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + p := lfs.Pointer{Oid: "83de2e488b89a0aa1c97496b888120a28b0c1e15463a4adb8405578c540f36d4", Size: 6} + + contentStore := lfs.NewContentStore() + exist, err := contentStore.Exists(p) + assert.NoError(t, err) + assert.False(t, exist) + err = contentStore.Put(p, bytes.NewReader([]byte("dummy5"))) + assert.NoError(t, err) + + meta, err := git_model.GetLFSMetaObjectByOid(repo.ID, p.Oid) + assert.Nil(t, meta) + assert.Equal(t, git_model.ErrLFSObjectNotExist, err) + + t.Run("InvalidAccess", func(t *testing.T) { + req := newRequest(t, p, "invalid") + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + }) + + t.Run("ValidAccess", func(t *testing.T) { + req := newRequest(t, p, "dummy5") + + session.MakeRequest(t, req, http.StatusOK) + meta, err = git_model.GetLFSMetaObjectByOid(repo.ID, p.Oid) + assert.NoError(t, err) + assert.NotNil(t, meta) + }) + + // Cleanup + err = contentStore.Delete(p.RelativePath()) + assert.NoError(t, err) + }) + + t.Run("MetaAlreadyExists", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, lfs.Pointer{Oid: oid, Size: 6}, "") + + session.MakeRequest(t, req, http.StatusOK) + }) + + t.Run("HashMismatch", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, lfs.Pointer{Oid: "2581dd7bbc1fe44726de4b7dd806a087a978b9c5aec0a60481259e34be09b06a", Size: 1}, "a") + + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + }) + + t.Run("SizeMismatch", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, lfs.Pointer{Oid: "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", Size: 2}, "a") + + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + }) + + t.Run("Success", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + p := lfs.Pointer{Oid: "6ccce4863b70f258d691f59609d31b4502e1ba5199942d3bc5d35d17a4ce771d", Size: 5} + + req := newRequest(t, p, "gitea") + + session.MakeRequest(t, req, http.StatusOK) + + contentStore := lfs.NewContentStore() + exist, err := contentStore.Exists(p) + assert.NoError(t, err) + assert.True(t, exist) + + meta, err := git_model.GetLFSMetaObjectByOid(repo.ID, p.Oid) + assert.NoError(t, err) + assert.NotNil(t, meta) + }) +} + +func TestAPILFSVerify(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + setting.LFS.StartServer = true + + repo := createLFSTestRepository(t, "verify") + + content := []byte("dummy3") + oid := storeObjectInRepo(t, repo.ID, &content) + defer git_model.RemoveLFSMetaObjectByOid(repo.ID, oid) + + session := loginUser(t, "user2") + + newRequest := func(t testing.TB, p *lfs.Pointer) *http.Request { + req := NewRequestWithJSON(t, "POST", "/user2/lfs-verify-repo.git/info/lfs/verify", p) + req.Header.Set("Accept", lfs.MediaType) + req.Header.Set("Content-Type", lfs.MediaType) + return req + } + + t.Run("InvalidJsonRequest", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, nil) + + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + }) + + t.Run("InvalidPointer", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, &lfs.Pointer{}) + + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + }) + + t.Run("PointerNotExisting", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, &lfs.Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6}) + + session.MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("Success", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, &lfs.Pointer{Oid: oid, Size: 6}) + + session.MakeRequest(t, req, http.StatusOK) + }) +} diff --git a/tests/integration/api_repo_raw_test.go b/tests/integration/api_repo_raw_test.go new file mode 100644 index 0000000000..9793e12b42 --- /dev/null +++ b/tests/integration/api_repo_raw_test.go @@ -0,0 +1,38 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIReposRaw(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + for _, ref := range [...]string{ + "master", // Branch + "v1.1", // Tag + "65f1bf27bc3bf70f64657658635e66094edbcb4d", // Commit + } { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/raw/%s/README.md?token="+token, user.Name, ref) + resp := session.MakeRequest(t, req, http.StatusOK) + assert.EqualValues(t, "file", resp.Header().Get("x-gitea-object-type")) + } + // Test default branch + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/raw/README.md?token="+token, user.Name) + resp := session.MakeRequest(t, req, http.StatusOK) + assert.EqualValues(t, "file", resp.Header().Get("x-gitea-object-type")) +} diff --git a/tests/integration/api_repo_tags_test.go b/tests/integration/api_repo_tags_test.go new file mode 100644 index 0000000000..5d3a209a76 --- /dev/null +++ b/tests/integration/api_repo_tags_test.go @@ -0,0 +1,84 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIRepoTags(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + repoName := "repo1" + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/tags?token=%s", user.Name, repoName, token) + resp := session.MakeRequest(t, req, http.StatusOK) + + var tags []*api.Tag + DecodeJSON(t, resp, &tags) + + assert.Len(t, tags, 1) + assert.Equal(t, "v1.1", tags[0].Name) + assert.Equal(t, "Initial commit", tags[0].Message) + assert.Equal(t, "65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.SHA) + assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", tags[0].Commit.URL) + assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.zip", tags[0].ZipballURL) + assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.tar.gz", tags[0].TarballURL) + + newTag := createNewTagUsingAPI(t, session, token, user.Name, repoName, "gitea/22", "", "nice!\nand some text") + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &tags) + assert.Len(t, tags, 2) + for _, tag := range tags { + if tag.Name != "v1.1" { + assert.EqualValues(t, newTag.Name, tag.Name) + assert.EqualValues(t, newTag.Message, tag.Message) + assert.EqualValues(t, "nice!\nand some text", tag.Message) + assert.EqualValues(t, newTag.Commit.SHA, tag.Commit.SHA) + } + } + + // get created tag + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/tags/%s?token=%s", user.Name, repoName, newTag.Name, token) + resp = session.MakeRequest(t, req, http.StatusOK) + var tag *api.Tag + DecodeJSON(t, resp, &tag) + assert.EqualValues(t, newTag, tag) + + // delete tag + delReq := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/tags/%s?token=%s", user.Name, repoName, newTag.Name, token) + session.MakeRequest(t, delReq, http.StatusNoContent) + + // check if it's gone + session.MakeRequest(t, req, http.StatusNotFound) +} + +func createNewTagUsingAPI(t *testing.T, session *TestSession, token, ownerName, repoName, name, target, msg string) *api.Tag { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/tags?token=%s", ownerName, repoName, token) + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateTagOption{ + TagName: name, + Message: msg, + Target: target, + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + + var respObj api.Tag + DecodeJSON(t, resp, &respObj) + return &respObj +} diff --git a/tests/integration/api_repo_teams_test.go b/tests/integration/api_repo_teams_test.go new file mode 100644 index 0000000000..1e476a89e2 --- /dev/null +++ b/tests/integration/api_repo_teams_test.go @@ -0,0 +1,82 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIRepoTeams(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // publicOrgRepo = user3/repo21 + publicOrgRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 32}) + // user4 + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + // ListTeams + url := fmt.Sprintf("/api/v1/repos/%s/teams?token=%s", publicOrgRepo.FullName(), token) + req := NewRequest(t, "GET", url) + res := session.MakeRequest(t, req, http.StatusOK) + var teams []*api.Team + DecodeJSON(t, res, &teams) + if assert.Len(t, teams, 2) { + assert.EqualValues(t, "Owners", teams[0].Name) + assert.True(t, teams[0].CanCreateOrgRepo) + assert.True(t, util.IsEqualSlice(unit.AllUnitKeyNames(), teams[0].Units), fmt.Sprintf("%v == %v", unit.AllUnitKeyNames(), teams[0].Units)) + assert.EqualValues(t, "owner", teams[0].Permission) + + assert.EqualValues(t, "test_team", teams[1].Name) + assert.False(t, teams[1].CanCreateOrgRepo) + assert.EqualValues(t, []string{"repo.issues"}, teams[1].Units) + assert.EqualValues(t, "write", teams[1].Permission) + } + + // IsTeam + url = fmt.Sprintf("/api/v1/repos/%s/teams/%s?token=%s", publicOrgRepo.FullName(), "Test_Team", token) + req = NewRequest(t, "GET", url) + res = session.MakeRequest(t, req, http.StatusOK) + var team *api.Team + DecodeJSON(t, res, &team) + assert.EqualValues(t, teams[1], team) + + url = fmt.Sprintf("/api/v1/repos/%s/teams/%s?token=%s", publicOrgRepo.FullName(), "NonExistingTeam", token) + req = NewRequest(t, "GET", url) + session.MakeRequest(t, req, http.StatusNotFound) + + // AddTeam with user4 + url = fmt.Sprintf("/api/v1/repos/%s/teams/%s?token=%s", publicOrgRepo.FullName(), "team1", token) + req = NewRequest(t, "PUT", url) + session.MakeRequest(t, req, http.StatusForbidden) + + // AddTeam with user2 + user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session = loginUser(t, user.Name) + token = getTokenForLoggedInUser(t, session) + url = fmt.Sprintf("/api/v1/repos/%s/teams/%s?token=%s", publicOrgRepo.FullName(), "team1", token) + req = NewRequest(t, "PUT", url) + session.MakeRequest(t, req, http.StatusNoContent) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) // test duplicate request + + // DeleteTeam + url = fmt.Sprintf("/api/v1/repos/%s/teams/%s?token=%s", publicOrgRepo.FullName(), "team1", token) + req = NewRequest(t, "DELETE", url) + session.MakeRequest(t, req, http.StatusNoContent) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) // test duplicate request +} diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go new file mode 100644 index 0000000000..483503ccbb --- /dev/null +++ b/tests/integration/api_repo_test.go @@ -0,0 +1,694 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "os" + "testing" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + access_model "code.gitea.io/gitea/models/perm/access" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIUserReposNotLogin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + req := NewRequestf(t, "GET", "/api/v1/users/%s/repos", user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var apiRepos []api.Repository + DecodeJSON(t, resp, &apiRepos) + expectedLen := unittest.GetCount(t, repo_model.Repository{OwnerID: user.ID}, + unittest.Cond("is_private = ?", false)) + assert.Len(t, apiRepos, expectedLen) + for _, repo := range apiRepos { + assert.EqualValues(t, user.ID, repo.Owner.ID) + assert.False(t, repo.Private) + } +} + +func TestAPISearchRepo(t *testing.T) { + defer tests.PrepareTestEnv(t)() + const keyword = "test" + + req := NewRequestf(t, "GET", "/api/v1/repos/search?q=%s", keyword) + resp := MakeRequest(t, req, http.StatusOK) + + var body api.SearchResults + DecodeJSON(t, resp, &body) + assert.NotEmpty(t, body.Data) + for _, repo := range body.Data { + assert.Contains(t, repo.Name, keyword) + assert.False(t, repo.Private) + } + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 16}) + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 18}) + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20}) + orgUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) + + oldAPIDefaultNum := setting.API.DefaultPagingNum + defer func() { + setting.API.DefaultPagingNum = oldAPIDefaultNum + }() + setting.API.DefaultPagingNum = 10 + + // Map of expected results, where key is user for login + type expectedResults map[*user_model.User]struct { + count int + repoOwnerID int64 + repoName string + includesPrivate bool + } + + testCases := []struct { + name, requestURL string + expectedResults + }{ + { + name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{ + nil: {count: 30}, + user: {count: 30}, + user2: {count: 30}, + }, + }, + { + name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10&private=false", expectedResults: expectedResults{ + nil: {count: 10}, + user: {count: 10}, + user2: {count: 10}, + }, + }, + { + name: "RepositoriesDefault", requestURL: "/api/v1/repos/search?default&private=false", expectedResults: expectedResults{ + nil: {count: 10}, + user: {count: 10}, + user2: {count: 10}, + }, + }, + { + name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "big_test_"), expectedResults: expectedResults{ + nil: {count: 7, repoName: "big_test_"}, + user: {count: 7, repoName: "big_test_"}, + user2: {count: 7, repoName: "big_test_"}, + }, + }, + { + name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "user2/big_test_"), expectedResults: expectedResults{ + user2: {count: 2, repoName: "big_test_"}, + }, + }, + { + name: "RepositoriesAccessibleAndRelatedToUser", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user.ID), expectedResults: expectedResults{ + nil: {count: 5}, + user: {count: 9, includesPrivate: true}, + user2: {count: 6, includesPrivate: true}, + }, + }, + { + name: "RepositoriesAccessibleAndRelatedToUser2", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user2.ID), expectedResults: expectedResults{ + nil: {count: 1}, + user: {count: 2, includesPrivate: true}, + user2: {count: 2, includesPrivate: true}, + user4: {count: 1}, + }, + }, + { + name: "RepositoriesAccessibleAndRelatedToUser3", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user3.ID), expectedResults: expectedResults{ + nil: {count: 1}, + user: {count: 4, includesPrivate: true}, + user2: {count: 3, includesPrivate: true}, + user3: {count: 4, includesPrivate: true}, + }, + }, + { + name: "RepositoriesOwnedByOrganization", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", orgUser.ID), expectedResults: expectedResults{ + nil: {count: 1, repoOwnerID: orgUser.ID}, + user: {count: 2, repoOwnerID: orgUser.ID, includesPrivate: true}, + user2: {count: 1, repoOwnerID: orgUser.ID}, + }, + }, + {name: "RepositoriesAccessibleAndRelatedToUser4", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user4.ID), expectedResults: expectedResults{ + nil: {count: 3}, + user: {count: 4, includesPrivate: true}, + user4: {count: 7, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeSource", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "source"), expectedResults: expectedResults{ + nil: {count: 0}, + user: {count: 1, includesPrivate: true}, + user4: {count: 1, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "fork"), expectedResults: expectedResults{ + nil: {count: 1}, + user: {count: 1}, + user4: {count: 2, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "fork"), expectedResults: expectedResults{ + nil: {count: 1}, + user: {count: 1}, + user4: {count: 2, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "mirror"), expectedResults: expectedResults{ + nil: {count: 2}, + user: {count: 2}, + user4: {count: 4, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "mirror"), expectedResults: expectedResults{ + nil: {count: 1}, + user: {count: 1}, + user4: {count: 2, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeCollaborative", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "collaborative"), expectedResults: expectedResults{ + nil: {count: 0}, + user: {count: 1, includesPrivate: true}, + user4: {count: 1, includesPrivate: true}, + }}, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + for userToLogin, expected := range testCase.expectedResults { + var session *TestSession + var testName string + var userID int64 + var token string + if userToLogin != nil && userToLogin.ID > 0 { + testName = fmt.Sprintf("LoggedUser%d", userToLogin.ID) + session = loginUser(t, userToLogin.Name) + token = getTokenForLoggedInUser(t, session) + userID = userToLogin.ID + } else { + testName = "AnonymousUser" + session = emptyTestSession(t) + } + + t.Run(testName, func(t *testing.T) { + request := NewRequest(t, "GET", testCase.requestURL+"&token="+token) + response := session.MakeRequest(t, request, http.StatusOK) + + var body api.SearchResults + DecodeJSON(t, response, &body) + + repoNames := make([]string, 0, len(body.Data)) + for _, repo := range body.Data { + repoNames = append(repoNames, fmt.Sprintf("%d:%s:%t", repo.ID, repo.FullName, repo.Private)) + } + assert.Len(t, repoNames, expected.count) + for _, repo := range body.Data { + r := getRepo(t, repo.ID) + hasAccess, err := access_model.HasAccess(db.DefaultContext, userID, r) + assert.NoError(t, err, "Error when checking if User: %d has access to %s: %v", userID, repo.FullName, err) + assert.True(t, hasAccess, "User: %d does not have access to %s", userID, repo.FullName) + + assert.NotEmpty(t, repo.Name) + assert.Equal(t, repo.Name, r.Name) + + if len(expected.repoName) > 0 { + assert.Contains(t, repo.Name, expected.repoName) + } + + if expected.repoOwnerID > 0 { + assert.Equal(t, expected.repoOwnerID, repo.Owner.ID) + } + + if !expected.includesPrivate { + assert.False(t, repo.Private, "User: %d not expecting private repository: %s", userID, repo.FullName) + } + } + }) + } + }) + } +} + +var repoCache = make(map[int64]*repo_model.Repository) + +func getRepo(t *testing.T, repoID int64) *repo_model.Repository { + if _, ok := repoCache[repoID]; !ok { + repoCache[repoID] = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) + } + return repoCache[repoID] +} + +func TestAPIViewRepo(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + var repo api.Repository + + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1") + resp := MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &repo) + assert.EqualValues(t, 1, repo.ID) + assert.EqualValues(t, "repo1", repo.Name) + assert.EqualValues(t, 2, repo.Releases) + assert.EqualValues(t, 1, repo.OpenIssues) + assert.EqualValues(t, 3, repo.OpenPulls) + + req = NewRequest(t, "GET", "/api/v1/repos/user12/repo10") + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &repo) + assert.EqualValues(t, 10, repo.ID) + assert.EqualValues(t, "repo10", repo.Name) + assert.EqualValues(t, 1, repo.OpenPulls) + assert.EqualValues(t, 1, repo.Forks) + + req = NewRequest(t, "GET", "/api/v1/repos/user5/repo4") + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &repo) + assert.EqualValues(t, 4, repo.ID) + assert.EqualValues(t, "repo4", repo.Name) + assert.EqualValues(t, 1, repo.Stars) +} + +func TestAPIOrgRepos(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + // User3 is an Org. Check their repos. + sourceOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + + expectedResults := map[*user_model.User]struct { + count int + includesPrivate bool + }{ + nil: {count: 1}, + user: {count: 3, includesPrivate: true}, + user2: {count: 3, includesPrivate: true}, + user3: {count: 1}, + } + + for userToLogin, expected := range expectedResults { + var session *TestSession + var testName string + var token string + if userToLogin != nil && userToLogin.ID > 0 { + testName = fmt.Sprintf("LoggedUser%d", userToLogin.ID) + session = loginUser(t, userToLogin.Name) + token = getTokenForLoggedInUser(t, session) + } else { + testName = "AnonymousUser" + session = emptyTestSession(t) + } + t.Run(testName, func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos?token="+token, sourceOrg.Name) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiRepos []*api.Repository + DecodeJSON(t, resp, &apiRepos) + assert.Len(t, apiRepos, expected.count) + for _, repo := range apiRepos { + if !expected.includesPrivate { + assert.False(t, repo.Private) + } + } + }) + } +} + +func TestAPIGetRepoByIDUnauthorized(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "GET", "/api/v1/repositories/2?token="+token) + session.MakeRequest(t, req, http.StatusNotFound) +} + +func TestAPIRepoMigrate(t *testing.T) { + testCases := []struct { + ctxUserID, userID int64 + cloneURL, repoName string + expectedStatus int + }{ + {ctxUserID: 1, userID: 2, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-admin", expectedStatus: http.StatusCreated}, + {ctxUserID: 2, userID: 2, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-own", expectedStatus: http.StatusCreated}, + {ctxUserID: 2, userID: 1, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-bad", expectedStatus: http.StatusForbidden}, + {ctxUserID: 2, userID: 3, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-org", expectedStatus: http.StatusCreated}, + {ctxUserID: 2, userID: 6, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-bad-org", expectedStatus: http.StatusForbidden}, + {ctxUserID: 2, userID: 3, cloneURL: "https://localhost:3000/user/test_repo.git", repoName: "private-ip", expectedStatus: http.StatusUnprocessableEntity}, + {ctxUserID: 2, userID: 3, cloneURL: "https://10.0.0.1/user/test_repo.git", repoName: "private-ip", expectedStatus: http.StatusUnprocessableEntity}, + } + + defer tests.PrepareTestEnv(t)() + for _, testCase := range testCases { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate?token="+token, &api.MigrateRepoOptions{ + CloneAddr: testCase.cloneURL, + RepoOwnerID: testCase.userID, + RepoName: testCase.repoName, + }) + resp := MakeRequest(t, req, NoExpectedStatus) + if resp.Code == http.StatusUnprocessableEntity { + respJSON := map[string]string{} + DecodeJSON(t, resp, &respJSON) + switch respJSON["message"] { + case "Remote visit addressed rate limitation.": + t.Log("test hit github rate limitation") + case "You can not import from disallowed hosts.": + assert.EqualValues(t, "private-ip", testCase.repoName) + default: + assert.Failf(t, "unexpected error '%v' on url '%s'", respJSON["message"], testCase.cloneURL) + } + } else { + assert.EqualValues(t, testCase.expectedStatus, resp.Code) + } + } +} + +func TestAPIRepoMigrateConflict(t *testing.T) { + onGiteaRun(t, testAPIRepoMigrateConflict) +} + +func testAPIRepoMigrateConflict(t *testing.T, u *url.URL) { + username := "user2" + baseAPITestContext := NewAPITestContext(t, username, "repo1") + + u.Path = baseAPITestContext.GitPath() + + t.Run("Existing", func(t *testing.T) { + httpContext := baseAPITestContext + + httpContext.Reponame = "repo-tmp-17" + dstPath, err := os.MkdirTemp("", httpContext.Reponame) + assert.NoError(t, err) + defer util.RemoveAll(dstPath) + t.Run("CreateRepo", doAPICreateRepository(httpContext, false)) + + user, err := user_model.GetUserByName(db.DefaultContext, httpContext.Username) + assert.NoError(t, err) + userID := user.ID + + cloneURL := "https://github.com/go-gitea/test_repo.git" + + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate?token="+httpContext.Token, + &api.MigrateRepoOptions{ + CloneAddr: cloneURL, + RepoOwnerID: userID, + RepoName: httpContext.Reponame, + }) + resp := httpContext.Session.MakeRequest(t, req, http.StatusConflict) + respJSON := map[string]string{} + DecodeJSON(t, resp, &respJSON) + assert.Equal(t, "The repository with the same name already exists.", respJSON["message"]) + }) +} + +// mirror-sync must fail with "400 (Bad Request)" when an attempt is made to +// sync a non-mirror repository. +func TestAPIMirrorSyncNonMirrorRepo(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + + var repo api.Repository + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1") + resp := MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &repo) + assert.EqualValues(t, false, repo.Mirror) + + req = NewRequestf(t, "POST", "/api/v1/repos/user2/repo1/mirror-sync?token=%s", token) + resp = session.MakeRequest(t, req, http.StatusBadRequest) + errRespJSON := map[string]string{} + DecodeJSON(t, resp, &errRespJSON) + assert.Equal(t, "Repository is not a mirror", errRespJSON["message"]) +} + +func TestAPIOrgRepoCreate(t *testing.T) { + testCases := []struct { + ctxUserID int64 + orgName, repoName string + expectedStatus int + }{ + {ctxUserID: 1, orgName: "user3", repoName: "repo-admin", expectedStatus: http.StatusCreated}, + {ctxUserID: 2, orgName: "user3", repoName: "repo-own", expectedStatus: http.StatusCreated}, + {ctxUserID: 2, orgName: "user6", repoName: "repo-bad-org", expectedStatus: http.StatusForbidden}, + {ctxUserID: 28, orgName: "user3", repoName: "repo-creator", expectedStatus: http.StatusCreated}, + {ctxUserID: 28, orgName: "user6", repoName: "repo-not-creator", expectedStatus: http.StatusForbidden}, + } + + defer tests.PrepareTestEnv(t)() + for _, testCase := range testCases { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos?token="+token, testCase.orgName), &api.CreateRepoOption{ + Name: testCase.repoName, + }) + session.MakeRequest(t, req, testCase.expectedStatus) + } +} + +func TestAPIRepoCreateConflict(t *testing.T) { + onGiteaRun(t, testAPIRepoCreateConflict) +} + +func testAPIRepoCreateConflict(t *testing.T, u *url.URL) { + username := "user2" + baseAPITestContext := NewAPITestContext(t, username, "repo1") + + u.Path = baseAPITestContext.GitPath() + + t.Run("Existing", func(t *testing.T) { + httpContext := baseAPITestContext + + httpContext.Reponame = "repo-tmp-17" + dstPath, err := os.MkdirTemp("", httpContext.Reponame) + assert.NoError(t, err) + defer util.RemoveAll(dstPath) + t.Run("CreateRepo", doAPICreateRepository(httpContext, false)) + + req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos?token="+httpContext.Token, + &api.CreateRepoOption{ + Name: httpContext.Reponame, + }) + resp := httpContext.Session.MakeRequest(t, req, http.StatusConflict) + respJSON := map[string]string{} + DecodeJSON(t, resp, &respJSON) + assert.Equal(t, respJSON["message"], "The repository with the same name already exists.") + }) +} + +func TestAPIRepoTransfer(t *testing.T) { + testCases := []struct { + ctxUserID int64 + newOwner string + teams *[]int64 + expectedStatus int + }{ + // Disclaimer for test story: "user1" is an admin, "user2" is normal user and part of in owner team of org "user3" + // Transfer to a user with teams in another org should fail + {ctxUserID: 1, newOwner: "user3", teams: &[]int64{5}, expectedStatus: http.StatusForbidden}, + // Transfer to a user with non-existent team IDs should fail + {ctxUserID: 1, newOwner: "user2", teams: &[]int64{2}, expectedStatus: http.StatusUnprocessableEntity}, + // Transfer should go through + {ctxUserID: 1, newOwner: "user3", teams: &[]int64{2}, expectedStatus: http.StatusAccepted}, + // Let user transfer it back to himself + {ctxUserID: 2, newOwner: "user2", expectedStatus: http.StatusAccepted}, + // And revert transfer + {ctxUserID: 2, newOwner: "user3", teams: &[]int64{2}, expectedStatus: http.StatusAccepted}, + // Cannot start transfer to an existing repo + {ctxUserID: 2, newOwner: "user3", teams: nil, expectedStatus: http.StatusUnprocessableEntity}, + // Start transfer, repo is now in pending transfer mode + {ctxUserID: 2, newOwner: "user6", teams: nil, expectedStatus: http.StatusCreated}, + } + + defer tests.PrepareTestEnv(t)() + + // create repo to move + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + repoName := "moveME" + apiRepo := new(api.Repository) + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/user/repos?token=%s", token), &api.CreateRepoOption{ + Name: repoName, + Description: "repo move around", + Private: false, + Readme: "Default", + AutoInit: true, + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, apiRepo) + + // start testing + for _, testCase := range testCases { + user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) + session = loginUser(t, user.Name) + token = getTokenForLoggedInUser(t, session) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer?token=%s", repo.OwnerName, repo.Name, token), &api.TransferRepoOption{ + NewOwner: testCase.newOwner, + TeamIDs: testCase.teams, + }) + session.MakeRequest(t, req, testCase.expectedStatus) + } + + // cleanup + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) + _ = models.DeleteRepository(user, repo.OwnerID, repo.ID) +} + +func transfer(t *testing.T) *repo_model.Repository { + // create repo to move + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + repoName := "moveME" + apiRepo := new(api.Repository) + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/user/repos?token=%s", token), &api.CreateRepoOption{ + Name: repoName, + Description: "repo move around", + Private: false, + Readme: "Default", + AutoInit: true, + }) + + resp := session.MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, apiRepo) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer?token=%s", repo.OwnerName, repo.Name, token), &api.TransferRepoOption{ + NewOwner: "user4", + }) + session.MakeRequest(t, req, http.StatusCreated) + + return repo +} + +func TestAPIAcceptTransfer(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := transfer(t) + + // try to accept with not authorized user + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject?token=%s", repo.OwnerName, repo.Name, token)) + session.MakeRequest(t, req, http.StatusForbidden) + + // try to accept repo that's not marked as transferred + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/accept?token=%s", "user2", "repo1", token)) + session.MakeRequest(t, req, http.StatusNotFound) + + // accept transfer + session = loginUser(t, "user4") + token = getTokenForLoggedInUser(t, session) + + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/accept?token=%s", repo.OwnerName, repo.Name, token)) + resp := session.MakeRequest(t, req, http.StatusAccepted) + apiRepo := new(api.Repository) + DecodeJSON(t, resp, apiRepo) + assert.Equal(t, "user4", apiRepo.Owner.UserName) +} + +func TestAPIRejectTransfer(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := transfer(t) + + // try to reject with not authorized user + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject?token=%s", repo.OwnerName, repo.Name, token)) + session.MakeRequest(t, req, http.StatusForbidden) + + // try to reject repo that's not marked as transferred + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject?token=%s", "user2", "repo1", token)) + session.MakeRequest(t, req, http.StatusNotFound) + + // reject transfer + session = loginUser(t, "user4") + token = getTokenForLoggedInUser(t, session) + + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject?token=%s", repo.OwnerName, repo.Name, token)) + resp := session.MakeRequest(t, req, http.StatusOK) + apiRepo := new(api.Repository) + DecodeJSON(t, resp, apiRepo) + assert.Equal(t, "user2", apiRepo.Owner.UserName) +} + +func TestAPIGenerateRepo(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + templateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 44}) + + // user + repo := new(api.Repository) + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate?token=%s", templateRepo.OwnerName, templateRepo.Name, token), &api.GenerateRepoOption{ + Owner: user.Name, + Name: "new-repo", + Description: "test generate repo", + Private: false, + GitContent: true, + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, repo) + + assert.Equal(t, "new-repo", repo.Name) + + // org + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate?token=%s", templateRepo.OwnerName, templateRepo.Name, token), &api.GenerateRepoOption{ + Owner: "user3", + Name: "new-repo", + Description: "test generate repo", + Private: false, + GitContent: true, + }) + resp = session.MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, repo) + + assert.Equal(t, "new-repo", repo.Name) +} + +func TestAPIRepoGetReviewers(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/reviewers?token=%s", user.Name, repo.Name, token) + resp := session.MakeRequest(t, req, http.StatusOK) + var reviewers []*api.User + DecodeJSON(t, resp, &reviewers) + assert.Len(t, reviewers, 4) +} + +func TestAPIRepoGetAssignees(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees?token=%s", user.Name, repo.Name, token) + resp := session.MakeRequest(t, req, http.StatusOK) + var assignees []*api.User + DecodeJSON(t, resp, &assignees) + assert.Len(t, assignees, 1) +} diff --git a/tests/integration/api_repo_topic_test.go b/tests/integration/api_repo_topic_test.go new file mode 100644 index 0000000000..4e1e293890 --- /dev/null +++ b/tests/integration/api_repo_topic_test.go @@ -0,0 +1,155 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPITopicSearch(t *testing.T) { + defer tests.PrepareTestEnv(t)() + searchURL, _ := url.Parse("/api/v1/topics/search") + var topics struct { + TopicNames []*api.TopicResponse `json:"topics"` + } + + query := url.Values{"page": []string{"1"}, "limit": []string{"4"}} + + searchURL.RawQuery = query.Encode() + res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) + DecodeJSON(t, res, &topics) + assert.Len(t, topics.TopicNames, 4) + assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + + query.Add("q", "topic") + searchURL.RawQuery = query.Encode() + res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) + DecodeJSON(t, res, &topics) + assert.Len(t, topics.TopicNames, 2) + + query.Set("q", "database") + searchURL.RawQuery = query.Encode() + res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) + DecodeJSON(t, res, &topics) + if assert.Len(t, topics.TopicNames, 1) { + assert.EqualValues(t, 2, topics.TopicNames[0].ID) + assert.EqualValues(t, "database", topics.TopicNames[0].Name) + assert.EqualValues(t, 1, topics.TopicNames[0].RepoCount) + } +} + +func TestAPIRepoTopic(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of repo2 + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of repo3 + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // write access to repo 3 + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + + // Get user2's token + token2 := getUserToken(t, user2.Name) + + // Test read topics using login + url := fmt.Sprintf("/api/v1/repos/%s/%s/topics", user2.Name, repo2.Name) + req := NewRequest(t, "GET", url+"?token="+token2) + res := MakeRequest(t, req, http.StatusOK) + var topics *api.TopicName + DecodeJSON(t, res, &topics) + assert.ElementsMatch(t, []string{"topicname1", "topicname2"}, topics.TopicNames) + + // Log out user2 + url = fmt.Sprintf("/api/v1/repos/%s/%s/topics?token=%s", user2.Name, repo2.Name, token2) + + // Test delete a topic + req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "Topicname1", token2) + MakeRequest(t, req, http.StatusNoContent) + + // Test add an existing topic + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "Golang", token2) + MakeRequest(t, req, http.StatusNoContent) + + // Test add a topic + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "topicName3", token2) + MakeRequest(t, req, http.StatusNoContent) + + // Test read topics using token + req = NewRequest(t, "GET", url) + res = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, res, &topics) + assert.ElementsMatch(t, []string{"topicname2", "golang", "topicname3"}, topics.TopicNames) + + // Test replace topics + newTopics := []string{" windows ", " ", "MAC "} + req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ + Topics: newTopics, + }) + MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "GET", url) + res = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, res, &topics) + assert.ElementsMatch(t, []string{"windows", "mac"}, topics.TopicNames) + + // Test replace topics with something invalid + newTopics = []string{"topicname1", "topicname2", "topicname!"} + req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ + Topics: newTopics, + }) + MakeRequest(t, req, http.StatusUnprocessableEntity) + req = NewRequest(t, "GET", url) + res = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, res, &topics) + assert.ElementsMatch(t, []string{"windows", "mac"}, topics.TopicNames) + + // Test with some topics multiple times, less than 25 unique + newTopics = []string{"t1", "t2", "t1", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10", "t11", "t12", "t13", "t14", "t15", "t16", "17", "t18", "t19", "t20", "t21", "t22", "t23", "t24", "t25"} + req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ + Topics: newTopics, + }) + MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "GET", url) + res = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, res, &topics) + assert.Len(t, topics.TopicNames, 25) + + // Test writing more topics than allowed + newTopics = append(newTopics, "t26") + req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ + Topics: newTopics, + }) + MakeRequest(t, req, http.StatusUnprocessableEntity) + + // Test add a topic when there is already maximum + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "t26", token2) + MakeRequest(t, req, http.StatusUnprocessableEntity) + + // Test delete a topic that repo doesn't have + req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "Topicname1", token2) + MakeRequest(t, req, http.StatusNotFound) + + // Get user4's token + token4 := getUserToken(t, user4.Name) + + // Test read topics with write access + url = fmt.Sprintf("/api/v1/repos/%s/%s/topics?token=%s", user3.Name, repo3.Name, token4) + req = NewRequest(t, "GET", url) + res = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, res, &topics) + assert.Empty(t, topics.TopicNames) + + // Test add a topic to repo with write access (requires repo admin access) + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user3.Name, repo3.Name, "topicName", token4) + MakeRequest(t, req, http.StatusForbidden) +} diff --git a/tests/integration/api_settings_test.go b/tests/integration/api_settings_test.go new file mode 100644 index 0000000000..b8da17b963 --- /dev/null +++ b/tests/integration/api_settings_test.go @@ -0,0 +1,65 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIExposedSettings(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + ui := new(api.GeneralUISettings) + req := NewRequest(t, "GET", "/api/v1/settings/ui") + resp := MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &ui) + assert.Len(t, ui.AllowedReactions, len(setting.UI.Reactions)) + assert.ElementsMatch(t, setting.UI.Reactions, ui.AllowedReactions) + + apiSettings := new(api.GeneralAPISettings) + req = NewRequest(t, "GET", "/api/v1/settings/api") + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &apiSettings) + assert.EqualValues(t, &api.GeneralAPISettings{ + MaxResponseItems: setting.API.MaxResponseItems, + DefaultPagingNum: setting.API.DefaultPagingNum, + DefaultGitTreesPerPage: setting.API.DefaultGitTreesPerPage, + DefaultMaxBlobSize: setting.API.DefaultMaxBlobSize, + }, apiSettings) + + repo := new(api.GeneralRepoSettings) + req = NewRequest(t, "GET", "/api/v1/settings/repository") + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &repo) + assert.EqualValues(t, &api.GeneralRepoSettings{ + MirrorsDisabled: !setting.Mirror.Enabled, + HTTPGitDisabled: setting.Repository.DisableHTTPGit, + MigrationsDisabled: setting.Repository.DisableMigrations, + TimeTrackingDisabled: false, + LFSDisabled: !setting.LFS.StartServer, + }, repo) + + attachment := new(api.GeneralAttachmentSettings) + req = NewRequest(t, "GET", "/api/v1/settings/attachment") + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &attachment) + assert.EqualValues(t, &api.GeneralAttachmentSettings{ + Enabled: setting.Attachment.Enabled, + AllowedTypes: setting.Attachment.AllowedTypes, + MaxFiles: setting.Attachment.MaxFiles, + MaxSize: setting.Attachment.MaxSize, + }, attachment) +} diff --git a/tests/integration/api_team_test.go b/tests/integration/api_team_test.go new file mode 100644 index 0000000000..a667949c09 --- /dev/null +++ b/tests/integration/api_team_test.go @@ -0,0 +1,268 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "sort" + "testing" + + "code.gitea.io/gitea/models/organization" + "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/convert" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPITeam(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + teamUser := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{}) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamUser.TeamID}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser.UID}) + + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamUser.TeamID) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiTeam api.Team + DecodeJSON(t, resp, &apiTeam) + assert.EqualValues(t, team.ID, apiTeam.ID) + assert.Equal(t, team.Name, apiTeam.Name) + + // non team member user will not access the teams details + teamUser2 := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{ID: 3}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser2.UID}) + + session = loginUser(t, user2.Name) + token = getTokenForLoggedInUser(t, session) + req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamUser.TeamID) + _ = session.MakeRequest(t, req, http.StatusForbidden) + + req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamUser.TeamID) + _ = session.MakeRequest(t, req, http.StatusUnauthorized) + + // Get an admin user able to create, update and delete teams. + user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + session = loginUser(t, user.Name) + token = getTokenForLoggedInUser(t, session) + + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 6}) + + // Create team. + teamToCreate := &api.CreateTeamOption{ + Name: "team1", + Description: "team one", + IncludesAllRepositories: true, + Permission: "write", + Units: []string{"repo.code", "repo.issues"}, + } + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", org.Name, token), teamToCreate) + resp = session.MakeRequest(t, req, http.StatusCreated) + apiTeam = api.Team{} + DecodeJSON(t, resp, &apiTeam) + checkTeamResponse(t, "CreateTeam1", &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, + teamToCreate.Permission, teamToCreate.Units, nil) + checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, + teamToCreate.Permission, teamToCreate.Units, nil) + teamID := apiTeam.ID + + // Edit team. + editDescription := "team 1" + editFalse := false + teamToEdit := &api.EditTeamOption{ + Name: "teamone", + Description: &editDescription, + Permission: "admin", + IncludesAllRepositories: &editFalse, + Units: []string{"repo.code", "repo.pulls", "repo.releases"}, + } + + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEdit) + resp = session.MakeRequest(t, req, http.StatusOK) + apiTeam = api.Team{} + DecodeJSON(t, resp, &apiTeam) + checkTeamResponse(t, "EditTeam1", &apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, + teamToEdit.Permission, unit.AllUnitKeyNames(), nil) + checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, + teamToEdit.Permission, unit.AllUnitKeyNames(), nil) + + // Edit team Description only + editDescription = "first team" + teamToEditDesc := api.EditTeamOption{Description: &editDescription} + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEditDesc) + resp = session.MakeRequest(t, req, http.StatusOK) + apiTeam = api.Team{} + DecodeJSON(t, resp, &apiTeam) + checkTeamResponse(t, "EditTeam1_DescOnly", &apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, + teamToEdit.Permission, unit.AllUnitKeyNames(), nil) + checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, + teamToEdit.Permission, unit.AllUnitKeyNames(), nil) + + // Read team. + teamRead := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) + assert.NoError(t, teamRead.GetUnits()) + req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamID) + resp = session.MakeRequest(t, req, http.StatusOK) + apiTeam = api.Team{} + DecodeJSON(t, resp, &apiTeam) + checkTeamResponse(t, "ReadTeam1", &apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories, + teamRead.AccessMode.String(), teamRead.GetUnitNames(), teamRead.GetUnitsMap()) + + // Delete team. + req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID) + session.MakeRequest(t, req, http.StatusNoContent) + unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) + + // create team again via UnitsMap + // Create team. + teamToCreate = &api.CreateTeamOption{ + Name: "team2", + Description: "team two", + IncludesAllRepositories: true, + Permission: "write", + UnitsMap: map[string]string{"repo.code": "read", "repo.issues": "write", "repo.wiki": "none"}, + } + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", org.Name, token), teamToCreate) + resp = session.MakeRequest(t, req, http.StatusCreated) + apiTeam = api.Team{} + DecodeJSON(t, resp, &apiTeam) + checkTeamResponse(t, "CreateTeam2", &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, + "read", nil, teamToCreate.UnitsMap) + checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, + "read", nil, teamToCreate.UnitsMap) + teamID = apiTeam.ID + + // Edit team. + editDescription = "team 1" + editFalse = false + teamToEdit = &api.EditTeamOption{ + Name: "teamtwo", + Description: &editDescription, + Permission: "write", + IncludesAllRepositories: &editFalse, + UnitsMap: map[string]string{"repo.code": "read", "repo.pulls": "read", "repo.releases": "write"}, + } + + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEdit) + resp = session.MakeRequest(t, req, http.StatusOK) + apiTeam = api.Team{} + DecodeJSON(t, resp, &apiTeam) + checkTeamResponse(t, "EditTeam2", &apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, + "read", nil, teamToEdit.UnitsMap) + checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, + "read", nil, teamToEdit.UnitsMap) + + // Edit team Description only + editDescription = "second team" + teamToEditDesc = api.EditTeamOption{Description: &editDescription} + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEditDesc) + resp = session.MakeRequest(t, req, http.StatusOK) + apiTeam = api.Team{} + DecodeJSON(t, resp, &apiTeam) + checkTeamResponse(t, "EditTeam2_DescOnly", &apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, + "read", nil, teamToEdit.UnitsMap) + checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, + "read", nil, teamToEdit.UnitsMap) + + // Read team. + teamRead = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) + req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamID) + resp = session.MakeRequest(t, req, http.StatusOK) + apiTeam = api.Team{} + DecodeJSON(t, resp, &apiTeam) + assert.NoError(t, teamRead.GetUnits()) + checkTeamResponse(t, "ReadTeam2", &apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories, + teamRead.AccessMode.String(), teamRead.GetUnitNames(), teamRead.GetUnitsMap()) + + // Delete team. + req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID) + session.MakeRequest(t, req, http.StatusNoContent) + unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) +} + +func checkTeamResponse(t *testing.T, testName string, apiTeam *api.Team, name, description string, includesAllRepositories bool, permission string, units []string, unitsMap map[string]string) { + t.Run(testName, func(t *testing.T) { + assert.Equal(t, name, apiTeam.Name, "name") + assert.Equal(t, description, apiTeam.Description, "description") + assert.Equal(t, includesAllRepositories, apiTeam.IncludesAllRepositories, "includesAllRepositories") + assert.Equal(t, permission, apiTeam.Permission, "permission") + if units != nil { + sort.StringSlice(units).Sort() + sort.StringSlice(apiTeam.Units).Sort() + assert.EqualValues(t, units, apiTeam.Units, "units") + } + if unitsMap != nil { + assert.EqualValues(t, unitsMap, apiTeam.UnitsMap, "unitsMap") + } + }) +} + +func checkTeamBean(t *testing.T, id int64, name, description string, includesAllRepositories bool, permission string, units []string, unitsMap map[string]string) { + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: id}) + assert.NoError(t, team.GetUnits(), "GetUnits") + apiTeam, err := convert.ToTeam(team) + assert.NoError(t, err) + checkTeamResponse(t, fmt.Sprintf("checkTeamBean/%s_%s", name, description), apiTeam, name, description, includesAllRepositories, permission, units, unitsMap) +} + +type TeamSearchResults struct { + OK bool `json:"ok"` + Data []*api.Team `json:"data"` +} + +func TestAPITeamSearch(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) + + var results TeamSearchResults + + token := getUserToken(t, user.Name) + req := NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s&token=%s", org.Name, "_team", token) + resp := MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &results) + assert.NotEmpty(t, results.Data) + assert.Len(t, results.Data, 1) + assert.Equal(t, "test_team", results.Data[0].Name) + + // no access if not organization member + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + token5 := getUserToken(t, user5.Name) + + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s&token=%s", org.Name, "team", token5) + MakeRequest(t, req, http.StatusForbidden) +} + +func TestAPIGetTeamRepo(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) + teamRepo := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 24}) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}) + + var results api.Repository + + token := getUserToken(t, user.Name) + req := NewRequestf(t, "GET", "/api/v1/teams/%d/repos/%s/?token=%s", team.ID, teamRepo.FullName(), token) + resp := MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &results) + assert.Equal(t, "big_test_private_4", teamRepo.Name) + + // no access if not organization member + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + token5 := getUserToken(t, user5.Name) + + req = NewRequestf(t, "GET", "/api/v1/teams/%d/repos/%s/?token=%s", team.ID, teamRepo.FullName(), token5) + MakeRequest(t, req, http.StatusNotFound) +} diff --git a/tests/integration/api_team_user_test.go b/tests/integration/api_team_user_test.go new file mode 100644 index 0000000000..b999b97a2b --- /dev/null +++ b/tests/integration/api_team_user_test.go @@ -0,0 +1,46 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + "time" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/convert" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPITeamUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + normalUsername := "user2" + session := loginUser(t, normalUsername) + token := getTokenForLoggedInUser(t, session) + req := NewRequest(t, "GET", "/api/v1/teams/1/members/user1?token="+token) + session.MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", "/api/v1/teams/1/members/user2?token="+token) + resp := session.MakeRequest(t, req, http.StatusOK) + var user2 *api.User + DecodeJSON(t, resp, &user2) + user2.Created = user2.Created.In(time.Local) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + + expectedUser := convert.ToUser(user, user) + + // test time via unix timestamp + assert.EqualValues(t, expectedUser.LastLogin.Unix(), user2.LastLogin.Unix()) + assert.EqualValues(t, expectedUser.Created.Unix(), user2.Created.Unix()) + expectedUser.LastLogin = user2.LastLogin + expectedUser.Created = user2.Created + + assert.Equal(t, expectedUser, user2) +} diff --git a/tests/integration/api_token_test.go b/tests/integration/api_token_test.go new file mode 100644 index 0000000000..023bf30179 --- /dev/null +++ b/tests/integration/api_token_test.go @@ -0,0 +1,66 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" +) + +// TestAPICreateAndDeleteToken tests that token that was just created can be deleted +func TestAPICreateAndDeleteToken(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + req := NewRequestWithJSON(t, "POST", "/api/v1/users/user1/tokens", map[string]string{ + "name": "test-key-1", + }) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusCreated) + + var newAccessToken api.AccessToken + DecodeJSON(t, resp, &newAccessToken) + unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{ + ID: newAccessToken.ID, + Name: newAccessToken.Name, + Token: newAccessToken.Token, + UID: user.ID, + }) + + req = NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", newAccessToken.ID) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNoContent) + + unittest.AssertNotExistsBean(t, &auth_model.AccessToken{ID: newAccessToken.ID}) + + req = NewRequestWithJSON(t, "POST", "/api/v1/users/user1/tokens", map[string]string{ + "name": "test-key-2", + }) + req = AddBasicAuthHeader(req, user.Name) + resp = MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, &newAccessToken) + + req = NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%s", newAccessToken.Name) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNoContent) + + unittest.AssertNotExistsBean(t, &auth_model.AccessToken{ID: newAccessToken.ID}) +} + +// TestAPIDeleteMissingToken ensures that error is thrown when token not found +func TestAPIDeleteMissingToken(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", unittest.NonexistentID) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNotFound) +} diff --git a/tests/integration/api_user_email_test.go b/tests/integration/api_user_email_test.go new file mode 100644 index 0000000000..7bd265187c --- /dev/null +++ b/tests/integration/api_user_email_test.go @@ -0,0 +1,112 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIListEmails(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + normalUsername := "user2" + session := loginUser(t, normalUsername) + token := getTokenForLoggedInUser(t, session) + + req := NewRequest(t, "GET", "/api/v1/user/emails?token="+token) + resp := session.MakeRequest(t, req, http.StatusOK) + + var emails []*api.Email + DecodeJSON(t, resp, &emails) + + assert.EqualValues(t, []*api.Email{ + { + Email: "user2@example.com", + Verified: true, + Primary: true, + }, + { + Email: "user2-2@example.com", + Verified: false, + Primary: false, + }, + }, emails) +} + +func TestAPIAddEmail(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + normalUsername := "user2" + session := loginUser(t, normalUsername) + token := getTokenForLoggedInUser(t, session) + + opts := api.CreateEmailOption{ + Emails: []string{"user101@example.com"}, + } + + req := NewRequestWithJSON(t, "POST", "/api/v1/user/emails?token="+token, &opts) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + opts = api.CreateEmailOption{ + Emails: []string{"user2-3@example.com"}, + } + req = NewRequestWithJSON(t, "POST", "/api/v1/user/emails?token="+token, &opts) + resp := session.MakeRequest(t, req, http.StatusCreated) + + var emails []*api.Email + DecodeJSON(t, resp, &emails) + assert.EqualValues(t, []*api.Email{ + { + Email: "user2-3@example.com", + Verified: true, + Primary: false, + }, + }, emails) + + opts = api.CreateEmailOption{ + Emails: []string{"notAEmail"}, + } + req = NewRequestWithJSON(t, "POST", "/api/v1/user/emails?token="+token, &opts) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) +} + +func TestAPIDeleteEmail(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + normalUsername := "user2" + session := loginUser(t, normalUsername) + token := getTokenForLoggedInUser(t, session) + + opts := api.DeleteEmailOption{ + Emails: []string{"user2-3@example.com"}, + } + req := NewRequestWithJSON(t, "DELETE", "/api/v1/user/emails?token="+token, &opts) + session.MakeRequest(t, req, http.StatusNotFound) + + opts = api.DeleteEmailOption{ + Emails: []string{"user2-2@example.com"}, + } + req = NewRequestWithJSON(t, "DELETE", "/api/v1/user/emails?token="+token, &opts) + session.MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "GET", "/api/v1/user/emails?token="+token) + resp := session.MakeRequest(t, req, http.StatusOK) + + var emails []*api.Email + DecodeJSON(t, resp, &emails) + assert.EqualValues(t, []*api.Email{ + { + Email: "user2@example.com", + Verified: true, + Primary: true, + }, + }, emails) +} diff --git a/tests/integration/api_user_heatmap_test.go b/tests/integration/api_user_heatmap_test.go new file mode 100644 index 0000000000..da6af0118d --- /dev/null +++ b/tests/integration/api_user_heatmap_test.go @@ -0,0 +1,39 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file.package models + +package integration + +import ( + "fmt" + "net/http" + "testing" + "time" + + activities_model "code.gitea.io/gitea/models/activities" + "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestUserHeatmap(t *testing.T) { + defer tests.PrepareTestEnv(t)() + adminUsername := "user1" + normalUsername := "user2" + token := getUserToken(t, adminUsername) + + fakeNow := time.Date(2011, 10, 20, 0, 0, 0, 0, time.Local) + timeutil.Set(fakeNow) + defer timeutil.Unset() + + urlStr := fmt.Sprintf("/api/v1/users/%s/heatmap?token=%s", normalUsername, token) + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + var heatmap []*activities_model.UserHeatmapData + DecodeJSON(t, resp, &heatmap) + var dummyheatmap []*activities_model.UserHeatmapData + dummyheatmap = append(dummyheatmap, &activities_model.UserHeatmapData{Timestamp: 1603227600, Contributions: 1}) + + assert.Equal(t, dummyheatmap, heatmap) +} diff --git a/tests/integration/api_user_org_perm_test.go b/tests/integration/api_user_org_perm_test.go new file mode 100644 index 0000000000..fef653545c --- /dev/null +++ b/tests/integration/api_user_org_perm_test.go @@ -0,0 +1,151 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +type apiUserOrgPermTestCase struct { + LoginUser string + User string + Organization string + ExpectedOrganizationPermissions api.OrganizationPermissions +} + +func TestTokenNeeded(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := emptyTestSession(t) + req := NewRequest(t, "GET", "/api/v1/users/user1/orgs/user6/permissions") + session.MakeRequest(t, req, http.StatusUnauthorized) +} + +func sampleTest(t *testing.T, auoptc apiUserOrgPermTestCase) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, auoptc.LoginUser) + token := getTokenForLoggedInUser(t, session) + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/orgs/%s/permissions?token=%s", auoptc.User, auoptc.Organization, token)) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiOP api.OrganizationPermissions + DecodeJSON(t, resp, &apiOP) + assert.Equal(t, auoptc.ExpectedOrganizationPermissions.IsOwner, apiOP.IsOwner) + assert.Equal(t, auoptc.ExpectedOrganizationPermissions.IsAdmin, apiOP.IsAdmin) + assert.Equal(t, auoptc.ExpectedOrganizationPermissions.CanWrite, apiOP.CanWrite) + assert.Equal(t, auoptc.ExpectedOrganizationPermissions.CanRead, apiOP.CanRead) + assert.Equal(t, auoptc.ExpectedOrganizationPermissions.CanCreateRepository, apiOP.CanCreateRepository) +} + +func TestWithOwnerUser(t *testing.T) { + sampleTest(t, apiUserOrgPermTestCase{ + LoginUser: "user2", + User: "user2", + Organization: "user3", + ExpectedOrganizationPermissions: api.OrganizationPermissions{ + IsOwner: true, + IsAdmin: true, + CanWrite: true, + CanRead: true, + CanCreateRepository: true, + }, + }) +} + +func TestCanWriteUser(t *testing.T) { + sampleTest(t, apiUserOrgPermTestCase{ + LoginUser: "user4", + User: "user4", + Organization: "user3", + ExpectedOrganizationPermissions: api.OrganizationPermissions{ + IsOwner: false, + IsAdmin: false, + CanWrite: true, + CanRead: true, + CanCreateRepository: false, + }, + }) +} + +func TestAdminUser(t *testing.T) { + sampleTest(t, apiUserOrgPermTestCase{ + LoginUser: "user1", + User: "user28", + Organization: "user3", + ExpectedOrganizationPermissions: api.OrganizationPermissions{ + IsOwner: false, + IsAdmin: true, + CanWrite: true, + CanRead: true, + CanCreateRepository: true, + }, + }) +} + +func TestAdminCanNotCreateRepo(t *testing.T) { + sampleTest(t, apiUserOrgPermTestCase{ + LoginUser: "user1", + User: "user28", + Organization: "user6", + ExpectedOrganizationPermissions: api.OrganizationPermissions{ + IsOwner: false, + IsAdmin: true, + CanWrite: true, + CanRead: true, + CanCreateRepository: false, + }, + }) +} + +func TestCanReadUser(t *testing.T) { + sampleTest(t, apiUserOrgPermTestCase{ + LoginUser: "user1", + User: "user24", + Organization: "org25", + ExpectedOrganizationPermissions: api.OrganizationPermissions{ + IsOwner: false, + IsAdmin: false, + CanWrite: false, + CanRead: true, + CanCreateRepository: false, + }, + }) +} + +func TestUnknowUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session) + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/unknow/orgs/org25/permissions?token=%s", token)) + resp := session.MakeRequest(t, req, http.StatusNotFound) + + var apiError api.APIError + DecodeJSON(t, resp, &apiError) + assert.Equal(t, "user redirect does not exist [name: unknow]", apiError.Message) +} + +func TestUnknowOrganization(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session) + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/user1/orgs/unknow/permissions?token=%s", token)) + resp := session.MakeRequest(t, req, http.StatusNotFound) + var apiError api.APIError + DecodeJSON(t, resp, &apiError) + assert.Equal(t, "GetUserByName", apiError.Message) +} diff --git a/tests/integration/api_user_orgs_test.go b/tests/integration/api_user_orgs_test.go new file mode 100644 index 0000000000..622dfdcf21 --- /dev/null +++ b/tests/integration/api_user_orgs_test.go @@ -0,0 +1,121 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file.package models + +package integration + +import ( + "fmt" + "net/http" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestUserOrgs(t *testing.T) { + defer tests.PrepareTestEnv(t)() + adminUsername := "user1" + normalUsername := "user2" + privateMemberUsername := "user4" + unrelatedUsername := "user5" + + orgs := getUserOrgs(t, adminUsername, normalUsername) + + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user3"}) + user17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user17"}) + + assert.Equal(t, []*api.Organization{ + { + ID: 17, + UserName: user17.Name, + FullName: user17.FullName, + AvatarURL: user17.AvatarLink(), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + { + ID: 3, + UserName: user3.Name, + FullName: user3.FullName, + AvatarURL: user3.AvatarLink(), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + }, orgs) + + // user itself should get it's org's he is a member of + orgs = getUserOrgs(t, privateMemberUsername, privateMemberUsername) + assert.Len(t, orgs, 1) + + // unrelated user should not get private org membership of privateMemberUsername + orgs = getUserOrgs(t, unrelatedUsername, privateMemberUsername) + assert.Len(t, orgs, 0) + + // not authenticated call also should hide org membership + orgs = getUserOrgs(t, "", privateMemberUsername) + assert.Len(t, orgs, 0) +} + +func getUserOrgs(t *testing.T, userDoer, userCheck string) (orgs []*api.Organization) { + token := "" + session := emptyTestSession(t) + if len(userDoer) != 0 { + session = loginUser(t, userDoer) + token = getTokenForLoggedInUser(t, session) + } + urlStr := fmt.Sprintf("/api/v1/users/%s/orgs?token=%s", userCheck, token) + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &orgs) + return orgs +} + +func TestMyOrgs(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := emptyTestSession(t) + req := NewRequest(t, "GET", "/api/v1/user/orgs") + session.MakeRequest(t, req, http.StatusUnauthorized) + + normalUsername := "user2" + session = loginUser(t, normalUsername) + token := getTokenForLoggedInUser(t, session) + req = NewRequest(t, "GET", "/api/v1/user/orgs?token="+token) + resp := session.MakeRequest(t, req, http.StatusOK) + var orgs []*api.Organization + DecodeJSON(t, resp, &orgs) + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user3"}) + user17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user17"}) + + assert.Equal(t, []*api.Organization{ + { + ID: 17, + UserName: user17.Name, + FullName: user17.FullName, + AvatarURL: user17.AvatarLink(), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + { + ID: 3, + UserName: user3.Name, + FullName: user3.FullName, + AvatarURL: user3.AvatarLink(), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + }, orgs) +} diff --git a/tests/integration/api_user_search_test.go b/tests/integration/api_user_search_test.go new file mode 100644 index 0000000000..9e9276077b --- /dev/null +++ b/tests/integration/api_user_search_test.go @@ -0,0 +1,94 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file.package models + +package integration + +import ( + "fmt" + "net/http" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +type SearchResults struct { + OK bool `json:"ok"` + Data []*api.User `json:"data"` +} + +func TestAPIUserSearchLoggedIn(t *testing.T) { + defer tests.PrepareTestEnv(t)() + adminUsername := "user1" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session) + query := "user2" + req := NewRequestf(t, "GET", "/api/v1/users/search?token=%s&q=%s", token, query) + resp := session.MakeRequest(t, req, http.StatusOK) + + var results SearchResults + DecodeJSON(t, resp, &results) + assert.NotEmpty(t, results.Data) + for _, user := range results.Data { + assert.Contains(t, user.UserName, query) + assert.NotEmpty(t, user.Email) + } +} + +func TestAPIUserSearchNotLoggedIn(t *testing.T) { + defer tests.PrepareTestEnv(t)() + query := "user2" + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) + resp := MakeRequest(t, req, http.StatusOK) + + var results SearchResults + DecodeJSON(t, resp, &results) + assert.NotEmpty(t, results.Data) + var modelUser *user_model.User + for _, user := range results.Data { + assert.Contains(t, user.UserName, query) + modelUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: user.ID}) + if modelUser.KeepEmailPrivate { + assert.EqualValues(t, fmt.Sprintf("%s@%s", modelUser.LowerName, setting.Service.NoReplyAddress), user.Email) + } else { + assert.EqualValues(t, modelUser.Email, user.Email) + } + } +} + +func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) { + defer tests.PrepareTestEnv(t)() + adminUsername := "user1" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session) + query := "user31" + req := NewRequestf(t, "GET", "/api/v1/users/search?token=%s&q=%s", token, query) + req.SetBasicAuth(token, "x-oauth-basic") + resp := session.MakeRequest(t, req, http.StatusOK) + + var results SearchResults + DecodeJSON(t, resp, &results) + assert.NotEmpty(t, results.Data) + for _, user := range results.Data { + assert.Contains(t, user.UserName, query) + assert.NotEmpty(t, user.Email) + assert.EqualValues(t, "private", user.Visibility) + } +} + +func TestAPIUserSearchNotLoggedInUserHidden(t *testing.T) { + defer tests.PrepareTestEnv(t)() + query := "user31" + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) + resp := MakeRequest(t, req, http.StatusOK) + + var results SearchResults + DecodeJSON(t, resp, &results) + assert.Empty(t, results.Data) +} diff --git a/tests/integration/api_wiki_test.go b/tests/integration/api_wiki_test.go new file mode 100644 index 0000000000..c6f4841d08 --- /dev/null +++ b/tests/integration/api_wiki_test.go @@ -0,0 +1,252 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "encoding/base64" + "fmt" + "net/http" + "testing" + + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIGetWikiPage(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + username := "user2" + session := loginUser(t, username) + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/Home", username, "repo1") + + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + var page *api.WikiPage + DecodeJSON(t, resp, &page) + + assert.Equal(t, &api.WikiPage{ + WikiPageMetaData: &api.WikiPageMetaData{ + Title: "Home", + HTMLURL: page.HTMLURL, + SubURL: "Home", + LastCommit: &api.WikiCommit{ + ID: "2c54faec6c45d31c1abfaecdab471eac6633738a", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Ethan Koenig", + Email: "ethantkoenig@gmail.com", + }, + Date: "2017-11-27T04:31:18Z", + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "Ethan Koenig", + Email: "ethantkoenig@gmail.com", + }, + Date: "2017-11-27T04:31:18Z", + }, + Message: "Add Home.md\n", + }, + }, + ContentBase64: base64.RawStdEncoding.EncodeToString( + []byte("# Home page\n\nThis is the home page!\n"), + ), + CommitCount: 1, + Sidebar: "", + Footer: "", + }, page) +} + +func TestAPIListWikiPages(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + username := "user2" + session := loginUser(t, username) + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/pages", username, "repo1") + + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + + var meta []*api.WikiPageMetaData + DecodeJSON(t, resp, &meta) + + dummymeta := []*api.WikiPageMetaData{ + { + Title: "Home", + HTMLURL: meta[0].HTMLURL, + SubURL: "Home", + LastCommit: &api.WikiCommit{ + ID: "2c54faec6c45d31c1abfaecdab471eac6633738a", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Ethan Koenig", + Email: "ethantkoenig@gmail.com", + }, + Date: "2017-11-27T04:31:18Z", + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "Ethan Koenig", + Email: "ethantkoenig@gmail.com", + }, + Date: "2017-11-27T04:31:18Z", + }, + Message: "Add Home.md\n", + }, + }, + { + Title: "Page With Image", + HTMLURL: meta[1].HTMLURL, + SubURL: "Page-With-Image", + LastCommit: &api.WikiCommit{ + ID: "0cf15c3f66ec8384480ed9c3cf87c9e97fbb0ec3", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Gabriel Silva Simões", + Email: "simoes.sgabriel@gmail.com", + }, + Date: "2019-01-25T01:41:55Z", + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "Gabriel Silva Simões", + Email: "simoes.sgabriel@gmail.com", + }, + Date: "2019-01-25T01:41:55Z", + }, + Message: "Add jpeg.jpg and page with image\n", + }, + }, + { + Title: "Page With Spaced Name", + HTMLURL: meta[2].HTMLURL, + SubURL: "Page-With-Spaced-Name", + LastCommit: &api.WikiCommit{ + ID: "c10d10b7e655b3dab1f53176db57c8219a5488d6", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Gabriel Silva Simões", + Email: "simoes.sgabriel@gmail.com", + }, + Date: "2019-01-25T01:39:51Z", + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "Gabriel Silva Simões", + Email: "simoes.sgabriel@gmail.com", + }, + Date: "2019-01-25T01:39:51Z", + }, + Message: "Add page with spaced name\n", + }, + }, + { + Title: "Unescaped File", + HTMLURL: meta[3].HTMLURL, + SubURL: "Unescaped-File", + LastCommit: &api.WikiCommit{ + ID: "0dca5bd9b5d7ef937710e056f575e86c0184ba85", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "6543", + Email: "6543@obermui.de", + }, + Date: "2021-07-19T16:42:46Z", + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "6543", + Email: "6543@obermui.de", + }, + Date: "2021-07-19T16:42:46Z", + }, + Message: "add unescaped file\n", + }, + }, + } + + assert.Equal(t, dummymeta, meta) +} + +func TestAPINewWikiPage(t *testing.T) { + for _, title := range []string{ + "New page", + "&&&&", + } { + defer tests.PrepareTestEnv(t)() + username := "user2" + session := loginUser(t, username) + token := getTokenForLoggedInUser(t, session) + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/new?token=%s", username, "repo1", token) + + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateWikiPageOptions{ + Title: title, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("Wiki page content for API unit tests")), + Message: "", + }) + session.MakeRequest(t, req, http.StatusCreated) + } +} + +func TestAPIEditWikiPage(t *testing.T) { + defer tests.PrepareTestEnv(t)() + username := "user2" + session := loginUser(t, username) + token := getTokenForLoggedInUser(t, session) + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/Page-With-Spaced-Name?token=%s", username, "repo1", token) + + req := NewRequestWithJSON(t, "PATCH", urlStr, &api.CreateWikiPageOptions{ + Title: "edited title", + ContentBase64: base64.StdEncoding.EncodeToString([]byte("Edited wiki page content for API unit tests")), + Message: "", + }) + session.MakeRequest(t, req, http.StatusOK) +} + +func TestAPIListPageRevisions(t *testing.T) { + defer tests.PrepareTestEnv(t)() + username := "user2" + session := loginUser(t, username) + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/revisions/Home", username, "repo1") + + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + + var revisions *api.WikiCommitList + DecodeJSON(t, resp, &revisions) + + dummyrevisions := &api.WikiCommitList{ + WikiCommits: []*api.WikiCommit{ + { + ID: "2c54faec6c45d31c1abfaecdab471eac6633738a", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Ethan Koenig", + Email: "ethantkoenig@gmail.com", + }, + Date: "2017-11-27T04:31:18Z", + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "Ethan Koenig", + Email: "ethantkoenig@gmail.com", + }, + Date: "2017-11-27T04:31:18Z", + }, + Message: "Add Home.md\n", + }, + }, + Count: 1, + } + + assert.Equal(t, dummyrevisions, revisions) +} diff --git a/tests/integration/attachment_test.go b/tests/integration/attachment_test.go new file mode 100644 index 0000000000..2d2c979f7b --- /dev/null +++ b/tests/integration/attachment_test.go @@ -0,0 +1,134 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "bytes" + "image" + "image/png" + "io" + "mime/multipart" + "net/http" + "strings" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/storage" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func generateImg() bytes.Buffer { + // Generate image + myImage := image.NewRGBA(image.Rect(0, 0, 32, 32)) + var buff bytes.Buffer + png.Encode(&buff, myImage) + return buff +} + +func createAttachment(t *testing.T, session *TestSession, repoURL, filename string, buff bytes.Buffer, expectedStatus int) string { + body := &bytes.Buffer{} + + // Setup multi-part + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("file", filename) + assert.NoError(t, err) + _, err = io.Copy(part, &buff) + assert.NoError(t, err) + err = writer.Close() + assert.NoError(t, err) + + csrf := GetCSRF(t, session, repoURL) + + req := NewRequestWithBody(t, "POST", repoURL+"/issues/attachments", body) + req.Header.Add("X-Csrf-Token", csrf) + req.Header.Add("Content-Type", writer.FormDataContentType()) + resp := session.MakeRequest(t, req, expectedStatus) + + if expectedStatus != http.StatusOK { + return "" + } + var obj map[string]string + DecodeJSON(t, resp, &obj) + return obj["uuid"] +} + +func TestCreateAnonymousAttachment(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := emptyTestSession(t) + createAttachment(t, session, "user2/repo1", "image.png", generateImg(), http.StatusSeeOther) +} + +func TestCreateIssueAttachment(t *testing.T) { + defer tests.PrepareTestEnv(t)() + const repoURL = "user2/repo1" + session := loginUser(t, "user2") + uuid := createAttachment(t, session, repoURL, "image.png", generateImg(), http.StatusOK) + + req := NewRequest(t, "GET", repoURL+"/issues/new") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + link, exists := htmlDoc.doc.Find("form#new-issue").Attr("action") + assert.True(t, exists, "The template has changed") + + postData := map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "title": "New Issue With Attachment", + "content": "some content", + "files": uuid, + } + + req = NewRequestWithValues(t, "POST", link, postData) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + test.RedirectURL(resp) // check that redirect URL exists + + // Validate that attachment is available + req = NewRequest(t, "GET", "/attachments/"+uuid) + session.MakeRequest(t, req, http.StatusOK) +} + +func TestGetAttachment(t *testing.T) { + defer tests.PrepareTestEnv(t)() + adminSession := loginUser(t, "user1") + user2Session := loginUser(t, "user2") + user8Session := loginUser(t, "user8") + emptySession := emptyTestSession(t) + testCases := []struct { + name string + uuid string + createFile bool + session *TestSession + want int + }{ + {"LinkedIssueUUID", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", true, user2Session, http.StatusOK}, + {"LinkedCommentUUID", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", true, user2Session, http.StatusOK}, + {"linked_release_uuid", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19", true, user2Session, http.StatusOK}, + {"NotExistingUUID", "b0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18", false, user2Session, http.StatusNotFound}, + {"FileMissing", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18", false, user2Session, http.StatusInternalServerError}, + {"NotLinked", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a20", true, user2Session, http.StatusNotFound}, + {"NotLinkedAccessibleByUploader", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a20", true, user8Session, http.StatusOK}, + {"PublicByNonLogged", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", true, emptySession, http.StatusOK}, + {"PrivateByNonLogged", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", true, emptySession, http.StatusNotFound}, + {"PrivateAccessibleByAdmin", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", true, adminSession, http.StatusOK}, + {"PrivateAccessibleByUser", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", true, user2Session, http.StatusOK}, + {"RepoNotAccessibleByUser", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", true, user8Session, http.StatusNotFound}, + {"OrgNotAccessibleByUser", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a21", true, user8Session, http.StatusNotFound}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Write empty file to be available for response + if tc.createFile { + _, err := storage.Attachments.Save(repo_model.AttachmentRelativePath(tc.uuid), strings.NewReader("hello world"), -1) + assert.NoError(t, err) + } + // Actual test + req := NewRequest(t, "GET", "/attachments/"+tc.uuid) + tc.session.MakeRequest(t, req, tc.want) + }) + } +} diff --git a/tests/integration/auth_ldap_test.go b/tests/integration/auth_ldap_test.go new file mode 100644 index 0000000000..f3c3e6d7b3 --- /dev/null +++ b/tests/integration/auth_ldap_test.go @@ -0,0 +1,415 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "context" + "net/http" + "os" + "strings" + "testing" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/services/auth" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +type ldapUser struct { + UserName string + Password string + FullName string + Email string + OtherEmails []string + IsAdmin bool + IsRestricted bool + SSHKeys []string +} + +var gitLDAPUsers = []ldapUser{ + { + UserName: "professor", + Password: "professor", + FullName: "Hubert Farnsworth", + Email: "professor@planetexpress.com", + OtherEmails: []string{"hubert@planetexpress.com"}, + IsAdmin: true, + }, + { + UserName: "hermes", + Password: "hermes", + FullName: "Conrad Hermes", + Email: "hermes@planetexpress.com", + SSHKeys: []string{ + "SHA256:qLY06smKfHoW/92yXySpnxFR10QFrLdRjf/GNPvwcW8", + "SHA256:QlVTuM5OssDatqidn2ffY+Lc4YA5Fs78U+0KOHI51jQ", + "SHA256:DXdeUKYOJCSSmClZuwrb60hUq7367j4fA+udNC3FdRI", + }, + IsAdmin: true, + }, + { + UserName: "fry", + Password: "fry", + FullName: "Philip Fry", + Email: "fry@planetexpress.com", + }, + { + UserName: "leela", + Password: "leela", + FullName: "Leela Turanga", + Email: "leela@planetexpress.com", + IsRestricted: true, + }, + { + UserName: "bender", + Password: "bender", + FullName: "Bender RodrÃguez", + Email: "bender@planetexpress.com", + }, +} + +var otherLDAPUsers = []ldapUser{ + { + UserName: "zoidberg", + Password: "zoidberg", + FullName: "John Zoidberg", + Email: "zoidberg@planetexpress.com", + }, + { + UserName: "amy", + Password: "amy", + FullName: "Amy Kroker", + Email: "amy@planetexpress.com", + }, +} + +func skipLDAPTests() bool { + return os.Getenv("TEST_LDAP") != "1" +} + +func getLDAPServerHost() string { + host := os.Getenv("TEST_LDAP_HOST") + if len(host) == 0 { + host = "ldap" + } + return host +} + +func addAuthSourceLDAP(t *testing.T, sshKeyAttribute string, groupMapParams ...string) { + groupTeamMapRemoval := "off" + groupTeamMap := "" + if len(groupMapParams) == 2 { + groupTeamMapRemoval = groupMapParams[0] + groupTeamMap = groupMapParams[1] + } + session := loginUser(t, "user1") + csrf := GetCSRF(t, session, "/admin/auths/new") + req := NewRequestWithValues(t, "POST", "/admin/auths/new", map[string]string{ + "_csrf": csrf, + "type": "2", + "name": "ldap", + "host": getLDAPServerHost(), + "port": "389", + "bind_dn": "uid=gitea,ou=service,dc=planetexpress,dc=com", + "bind_password": "password", + "user_base": "ou=people,dc=planetexpress,dc=com", + "filter": "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))", + "admin_filter": "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)", + "restricted_filter": "(uid=leela)", + "attribute_username": "uid", + "attribute_name": "givenName", + "attribute_surname": "sn", + "attribute_mail": "mail", + "attribute_ssh_public_key": sshKeyAttribute, + "is_sync_enabled": "on", + "is_active": "on", + "groups_enabled": "on", + "group_dn": "ou=people,dc=planetexpress,dc=com", + "group_member_uid": "member", + "group_team_map": groupTeamMap, + "group_team_map_removal": groupTeamMapRemoval, + "user_uid": "DN", + }) + session.MakeRequest(t, req, http.StatusSeeOther) +} + +func TestLDAPUserSignin(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "") + + u := gitLDAPUsers[0] + + session := loginUserWithPassword(t, u.UserName, u.Password) + req := NewRequest(t, "GET", "/user/settings") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + assert.Equal(t, u.UserName, htmlDoc.GetInputValueByName("name")) + assert.Equal(t, u.FullName, htmlDoc.GetInputValueByName("full_name")) + assert.Equal(t, u.Email, htmlDoc.Find(`label[for="email"]`).Siblings().First().Text()) +} + +func TestLDAPAuthChange(t *testing.T) { + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "") + + session := loginUser(t, "user1") + req := NewRequest(t, "GET", "/admin/auths") + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + href, exists := doc.Find("table.table td a").Attr("href") + if !exists { + assert.True(t, exists, "No authentication source found") + return + } + + req = NewRequest(t, "GET", href) + resp = session.MakeRequest(t, req, http.StatusOK) + doc = NewHTMLParser(t, resp.Body) + csrf := doc.GetCSRF() + host, _ := doc.Find(`input[name="host"]`).Attr("value") + assert.Equal(t, host, getLDAPServerHost()) + binddn, _ := doc.Find(`input[name="bind_dn"]`).Attr("value") + assert.Equal(t, binddn, "uid=gitea,ou=service,dc=planetexpress,dc=com") + + req = NewRequestWithValues(t, "POST", href, map[string]string{ + "_csrf": csrf, + "type": "2", + "name": "ldap", + "host": getLDAPServerHost(), + "port": "389", + "bind_dn": "uid=gitea,ou=service,dc=planetexpress,dc=com", + "bind_password": "password", + "user_base": "ou=people,dc=planetexpress,dc=com", + "filter": "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))", + "admin_filter": "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)", + "restricted_filter": "(uid=leela)", + "attribute_username": "uid", + "attribute_name": "givenName", + "attribute_surname": "sn", + "attribute_mail": "mail", + "attribute_ssh_public_key": "", + "is_sync_enabled": "on", + "is_active": "on", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + req = NewRequest(t, "GET", href) + resp = session.MakeRequest(t, req, http.StatusOK) + doc = NewHTMLParser(t, resp.Body) + host, _ = doc.Find(`input[name="host"]`).Attr("value") + assert.Equal(t, host, getLDAPServerHost()) + binddn, _ = doc.Find(`input[name="bind_dn"]`).Attr("value") + assert.Equal(t, binddn, "uid=gitea,ou=service,dc=planetexpress,dc=com") +} + +func TestLDAPUserSync(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "") + auth.SyncExternalUsers(context.Background(), true) + + session := loginUser(t, "user1") + // Check if users exists + for _, u := range gitLDAPUsers { + req := NewRequest(t, "GET", "/admin/users?q="+u.UserName) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + tr := htmlDoc.doc.Find("table.table tbody tr") + if !assert.True(t, tr.Length() == 1) { + continue + } + tds := tr.Find("td") + if !assert.True(t, tds.Length() > 0) { + continue + } + assert.Equal(t, u.UserName, strings.TrimSpace(tds.Find("td:nth-child(2) a").Text())) + assert.Equal(t, u.Email, strings.TrimSpace(tds.Find("td:nth-child(3) span").Text())) + if u.IsAdmin { + assert.True(t, tds.Find("td:nth-child(5) svg").HasClass("octicon-check")) + } else { + assert.True(t, tds.Find("td:nth-child(5) svg").HasClass("octicon-x")) + } + if u.IsRestricted { + assert.True(t, tds.Find("td:nth-child(6) svg").HasClass("octicon-check")) + } else { + assert.True(t, tds.Find("td:nth-child(6) svg").HasClass("octicon-x")) + } + } + + // Check if no users exist + for _, u := range otherLDAPUsers { + req := NewRequest(t, "GET", "/admin/users?q="+u.UserName) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + tr := htmlDoc.doc.Find("table.table tbody tr") + assert.True(t, tr.Length() == 0) + } +} + +func TestLDAPUserSigninFailed(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "") + + u := otherLDAPUsers[0] + testLoginFailed(t, u.UserName, u.Password, translation.NewLocale("en-US").Tr("form.username_password_incorrect")) +} + +func TestLDAPUserSSHKeySync(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "sshPublicKey") + + auth.SyncExternalUsers(context.Background(), true) + + // Check if users has SSH keys synced + for _, u := range gitLDAPUsers { + if len(u.SSHKeys) == 0 { + continue + } + session := loginUserWithPassword(t, u.UserName, u.Password) + + req := NewRequest(t, "GET", "/user/settings/keys") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + divs := htmlDoc.doc.Find(".key.list .print.meta") + + syncedKeys := make([]string, divs.Length()) + for i := 0; i < divs.Length(); i++ { + syncedKeys[i] = strings.TrimSpace(divs.Eq(i).Text()) + } + + assert.ElementsMatch(t, u.SSHKeys, syncedKeys, "Unequal number of keys synchronized for user: %s", u.UserName) + } +} + +func TestLDAPGroupTeamSyncAddMember(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "", "on", `{"cn=ship_crew,ou=people,dc=planetexpress,dc=com":{"org26": ["team11"]},"cn=admin_staff,ou=people,dc=planetexpress,dc=com": {"non-existent": ["non-existent"]}}`) + org, err := organization.GetOrgByName("org26") + assert.NoError(t, err) + team, err := organization.GetTeam(db.DefaultContext, org.ID, "team11") + assert.NoError(t, err) + auth.SyncExternalUsers(context.Background(), true) + for _, gitLDAPUser := range gitLDAPUsers { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: gitLDAPUser.UserName, + }) + usersOrgs, err := organization.FindOrgs(organization.FindOrgOptions{ + UserID: user.ID, + IncludePrivate: true, + }) + assert.NoError(t, err) + allOrgTeams, err := organization.GetUserOrgTeams(db.DefaultContext, org.ID, user.ID) + assert.NoError(t, err) + if user.Name == "fry" || user.Name == "leela" || user.Name == "bender" { + // assert members of LDAP group "cn=ship_crew" are added to mapped teams + assert.Equal(t, len(usersOrgs), 1, "User [%s] should be member of one organization", user.Name) + assert.Equal(t, usersOrgs[0].Name, "org26", "Membership should be added to the right organization") + isMember, err := organization.IsTeamMember(db.DefaultContext, usersOrgs[0].ID, team.ID, user.ID) + assert.NoError(t, err) + assert.True(t, isMember, "Membership should be added to the right team") + err = models.RemoveTeamMember(team, user.ID) + assert.NoError(t, err) + err = models.RemoveOrgUser(usersOrgs[0].ID, user.ID) + assert.NoError(t, err) + } else { + // assert members of LDAP group "cn=admin_staff" keep initial team membership since mapped team does not exist + assert.Empty(t, usersOrgs, "User should be member of no organization") + isMember, err := organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID) + assert.NoError(t, err) + assert.False(t, isMember, "User should no be added to this team") + assert.Empty(t, allOrgTeams, "User should not be added to any team") + } + } +} + +func TestLDAPGroupTeamSyncRemoveMember(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "", "on", `{"cn=dispatch,ou=people,dc=planetexpress,dc=com": {"org26": ["team11"]}}`) + org, err := organization.GetOrgByName("org26") + assert.NoError(t, err) + team, err := organization.GetTeam(db.DefaultContext, org.ID, "team11") + assert.NoError(t, err) + loginUserWithPassword(t, gitLDAPUsers[0].UserName, gitLDAPUsers[0].Password) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: gitLDAPUsers[0].UserName, + }) + err = organization.AddOrgUser(org.ID, user.ID) + assert.NoError(t, err) + err = models.AddTeamMember(team, user.ID) + assert.NoError(t, err) + isMember, err := organization.IsOrganizationMember(db.DefaultContext, org.ID, user.ID) + assert.NoError(t, err) + assert.True(t, isMember, "User should be member of this organization") + isMember, err = organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID) + assert.NoError(t, err) + assert.True(t, isMember, "User should be member of this team") + // assert team member "professor" gets removed from org26 team11 + loginUserWithPassword(t, gitLDAPUsers[0].UserName, gitLDAPUsers[0].Password) + isMember, err = organization.IsOrganizationMember(db.DefaultContext, org.ID, user.ID) + assert.NoError(t, err) + assert.False(t, isMember, "User membership should have been removed from organization") + isMember, err = organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID) + assert.NoError(t, err) + assert.False(t, isMember, "User membership should have been removed from team") +} + +// Login should work even if Team Group Map contains a broken JSON +func TestBrokenLDAPMapUserSignin(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "", "on", `{"NOT_A_VALID_JSON"["MISSING_DOUBLE_POINT"]}`) + + u := gitLDAPUsers[0] + + session := loginUserWithPassword(t, u.UserName, u.Password) + req := NewRequest(t, "GET", "/user/settings") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + assert.Equal(t, u.UserName, htmlDoc.GetInputValueByName("name")) + assert.Equal(t, u.FullName, htmlDoc.GetInputValueByName("full_name")) + assert.Equal(t, u.Email, htmlDoc.Find(`label[for="email"]`).Siblings().First().Text()) +} diff --git a/tests/integration/benchmarks_test.go b/tests/integration/benchmarks_test.go new file mode 100644 index 0000000000..bf66d221fb --- /dev/null +++ b/tests/integration/benchmarks_test.go @@ -0,0 +1,72 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "math/rand" + "net/http" + "net/url" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + api "code.gitea.io/gitea/modules/structs" +) + +// StringWithCharset random string (from https://www.calhoun.io/creating-random-strings-in-go/) +func StringWithCharset(length int, charset string) string { + b := make([]byte, length) + for i := range b { + b[i] = charset[rand.Intn(len(charset))] + } + return string(b) +} + +func BenchmarkRepoBranchCommit(b *testing.B) { + onGiteaRunTB(b, func(t testing.TB, u *url.URL) { + b := t.(*testing.B) + + samples := []int64{1, 2, 3} + b.ResetTimer() + + for _, repoID := range samples { + b.StopTimer() + repo := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: repoID}) + b.StartTimer() + b.Run(repo.Name, func(b *testing.B) { + session := loginUser(b, "user2") + b.ResetTimer() + b.Run("CreateBranch", func(b *testing.B) { + b.StopTimer() + branchName := StringWithCharset(5+rand.Intn(10), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + b.StartTimer() + for i := 0; i < b.N; i++ { + b.Run("new_"+branchName, func(b *testing.B) { + b.Skip("benchmark broken") // TODO fix + testAPICreateBranch(b, session, repo.OwnerName, repo.Name, repo.DefaultBranch, "new_"+branchName, http.StatusCreated) + }) + } + }) + b.Run("GetBranches", func(b *testing.B) { + req := NewRequestf(b, "GET", "/api/v1/repos/%s/branches", repo.FullName()) + session.MakeRequest(b, req, http.StatusOK) + }) + b.Run("AccessCommits", func(b *testing.B) { + var branches []*api.Branch + req := NewRequestf(b, "GET", "/api/v1/repos/%s/branches", repo.FullName()) + resp := session.MakeRequest(b, req, http.StatusOK) + DecodeJSON(b, resp, &branches) + b.ResetTimer() // We measure from here + if len(branches) != 0 { + for i := 0; i < b.N; i++ { + req := NewRequestf(b, "GET", "/api/v1/repos/%s/commits?sha=%s", repo.FullName(), branches[i%len(branches)].Name) + session.MakeRequest(b, req, http.StatusOK) + } + } + }) + }) + } + }) +} diff --git a/tests/integration/branches_test.go b/tests/integration/branches_test.go new file mode 100644 index 0000000000..bd0bd63c50 --- /dev/null +++ b/tests/integration/branches_test.go @@ -0,0 +1,76 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "testing" + + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestViewBranches(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/user2/repo1/branches") + resp := MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + _, exists := htmlDoc.doc.Find(".delete-branch-button").Attr("data-url") + assert.False(t, exists, "The template has changed") +} + +func TestDeleteBranch(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + deleteBranch(t) +} + +func TestUndoDeleteBranch(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + deleteBranch(t) + htmlDoc, name := branchAction(t, ".undo-button") + assert.Contains(t, + htmlDoc.doc.Find(".ui.positive.message").Text(), + translation.NewLocale("en-US").Tr("repo.branch.restore_success", name), + ) + }) +} + +func deleteBranch(t *testing.T) { + htmlDoc, name := branchAction(t, ".delete-branch-button") + assert.Contains(t, + htmlDoc.doc.Find(".ui.positive.message").Text(), + translation.NewLocale("en-US").Tr("repo.branch.deletion_success", name), + ) +} + +func branchAction(t *testing.T, button string) (*HTMLDoc, string) { + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2/repo1/branches") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + link, exists := htmlDoc.doc.Find(button).Attr("data-url") + if !assert.True(t, exists, "The template has changed") { + t.Skip() + } + + req = NewRequestWithValues(t, "POST", link, map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + }) + session.MakeRequest(t, req, http.StatusOK) + + url, err := url.Parse(link) + assert.NoError(t, err) + req = NewRequest(t, "GET", "/user2/repo1/branches") + resp = session.MakeRequest(t, req, http.StatusOK) + + return NewHTMLParser(t, resp.Body), url.Query().Get("name") +} diff --git a/tests/integration/change_default_branch_test.go b/tests/integration/change_default_branch_test.go new file mode 100644 index 0000000000..8edc0e63c4 --- /dev/null +++ b/tests/integration/change_default_branch_test.go @@ -0,0 +1,41 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/tests" +) + +func TestChangeDefaultBranch(t *testing.T) { + defer tests.PrepareTestEnv(t)() + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, owner.Name) + branchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name) + + csrf := GetCSRF(t, session, branchesURL) + req := NewRequestWithValues(t, "POST", branchesURL, map[string]string{ + "_csrf": csrf, + "action": "default_branch", + "branch": "DefaultBranch", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + csrf = GetCSRF(t, session, branchesURL) + req = NewRequestWithValues(t, "POST", branchesURL, map[string]string{ + "_csrf": csrf, + "action": "default_branch", + "branch": "does_not_exist", + }) + session.MakeRequest(t, req, http.StatusNotFound) +} diff --git a/tests/integration/cmd_keys_test.go b/tests/integration/cmd_keys_test.go new file mode 100644 index 0000000000..0c72956c29 --- /dev/null +++ b/tests/integration/cmd_keys_test.go @@ -0,0 +1,65 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "bytes" + "flag" + "io" + "net/url" + "os" + "testing" + + "code.gitea.io/gitea/cmd" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + + "github.com/urfave/cli" +) + +func Test_CmdKeys(t *testing.T) { + onGiteaRun(t, func(*testing.T, *url.URL) { + tests := []struct { + name string + args []string + wantErr bool + expectedOutput string + }{ + {"test_empty_1", []string{"keys", "--username=git", "--type=test", "--content=test"}, true, ""}, + {"test_empty_2", []string{"keys", "-e", "git", "-u", "git", "-t", "test", "-k", "test"}, true, ""}, + { + "with_key", + []string{"keys", "-e", "git", "-u", "git", "-t", "ssh-rsa", "-k", "AAAAB3NzaC1yc2EAAAADAQABAAABgQDWVj0fQ5N8wNc0LVNA41wDLYJ89ZIbejrPfg/avyj3u/ZohAKsQclxG4Ju0VirduBFF9EOiuxoiFBRr3xRpqzpsZtnMPkWVWb+akZwBFAx8p+jKdy4QXR/SZqbVobrGwip2UjSrri1CtBxpJikojRIZfCnDaMOyd9Jp6KkujvniFzUWdLmCPxUE9zhTaPu0JsEP7MW0m6yx7ZUhHyfss+NtqmFTaDO+QlMR7L2QkDliN2Jl3Xa3PhuWnKJfWhdAq1Cw4oraKUOmIgXLkuiuxVQ6mD3AiFupkmfqdHq6h+uHHmyQqv3gU+/sD8GbGAhf6ftqhTsXjnv1Aj4R8NoDf9BS6KRkzkeun5UisSzgtfQzjOMEiJtmrep2ZQrMGahrXa+q4VKr0aKJfm+KlLfwm/JztfsBcqQWNcTURiCFqz+fgZw0Ey/de0eyMzldYTdXXNRYCKjs9bvBK+6SSXRM7AhftfQ0ZuoW5+gtinPrnmoOaSCEJbAiEiTO/BzOHgowiM="}, + false, + "# gitea public key\ncommand=\"" + setting.AppPath + " --config=" + util.ShellEscape(setting.CustomConf) + " serv key-1\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDWVj0fQ5N8wNc0LVNA41wDLYJ89ZIbejrPfg/avyj3u/ZohAKsQclxG4Ju0VirduBFF9EOiuxoiFBRr3xRpqzpsZtnMPkWVWb+akZwBFAx8p+jKdy4QXR/SZqbVobrGwip2UjSrri1CtBxpJikojRIZfCnDaMOyd9Jp6KkujvniFzUWdLmCPxUE9zhTaPu0JsEP7MW0m6yx7ZUhHyfss+NtqmFTaDO+QlMR7L2QkDliN2Jl3Xa3PhuWnKJfWhdAq1Cw4oraKUOmIgXLkuiuxVQ6mD3AiFupkmfqdHq6h+uHHmyQqv3gU+/sD8GbGAhf6ftqhTsXjnv1Aj4R8NoDf9BS6KRkzkeun5UisSzgtfQzjOMEiJtmrep2ZQrMGahrXa+q4VKr0aKJfm+KlLfwm/JztfsBcqQWNcTURiCFqz+fgZw0Ey/de0eyMzldYTdXXNRYCKjs9bvBK+6SSXRM7AhftfQ0ZuoW5+gtinPrnmoOaSCEJbAiEiTO/BzOHgowiM= user2@localhost\n", + }, + {"invalid", []string{"keys", "--not-a-flag=git"}, true, "Incorrect Usage: flag provided but not defined: -not-a-flag\n\n"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + realStdout := os.Stdout // Backup Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + set := flag.NewFlagSet("keys", 0) + _ = set.Parse(tt.args) + context := cli.NewContext(&cli.App{Writer: os.Stdout}, set, nil) + err := cmd.CmdKeys.Run(context) + if (err != nil) != tt.wantErr { + t.Errorf("CmdKeys.Run() error = %v, wantErr %v", err, tt.wantErr) + } + w.Close() + var buf bytes.Buffer + io.Copy(&buf, r) + commandOutput := buf.String() + if tt.expectedOutput != commandOutput { + t.Errorf("expectedOutput: %#v, commandOutput: %#v", tt.expectedOutput, commandOutput) + } + // Restore stdout + os.Stdout = realStdout + }) + } + }) +} diff --git a/tests/integration/compare_test.go b/tests/integration/compare_test.go new file mode 100644 index 0000000000..7642109dd9 --- /dev/null +++ b/tests/integration/compare_test.go @@ -0,0 +1,42 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "strings" + "testing" + + "code.gitea.io/gitea/tests" + "github.com/stretchr/testify/assert" +) + +func TestCompareTag(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2/repo1/compare/v1.1...master") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + selection := htmlDoc.doc.Find(".choose.branch .filter.dropdown") + // A dropdown for both base and head. + assert.Lenf(t, selection.Nodes, 2, "The template has changed") + + req = NewRequest(t, "GET", "/user2/repo1/compare/invalid") + resp = session.MakeRequest(t, req, http.StatusNotFound) + assert.False(t, strings.Contains(resp.Body.String(), "/assets/img/500.png"), "expect 404 page not 500") +} + +// Compare with inferred default branch (master) +func TestCompareDefault(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2/repo1/compare/v1.1") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + selection := htmlDoc.doc.Find(".choose.branch .filter.dropdown") + assert.Lenf(t, selection.Nodes, 2, "The template has changed") +} diff --git a/tests/integration/cors_test.go b/tests/integration/cors_test.go new file mode 100644 index 0000000000..f531801627 --- /dev/null +++ b/tests/integration/cors_test.go @@ -0,0 +1,23 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/tests" + "github.com/stretchr/testify/assert" +) + +func TestCORSNotSet(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequestf(t, "GET", "/api/v1/version") + session := loginUser(t, "user2") + resp := session.MakeRequest(t, req, http.StatusOK) + assert.Equal(t, resp.Code, http.StatusOK) + corsHeader := resp.Header().Get("Access-Control-Allow-Origin") + assert.Equal(t, corsHeader, "", "Access-Control-Allow-Origin: generated header should match") // header not set +} diff --git a/tests/integration/create_no_session_test.go b/tests/integration/create_no_session_test.go new file mode 100644 index 0000000000..c9b90974d7 --- /dev/null +++ b/tests/integration/create_no_session_test.go @@ -0,0 +1,120 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "context" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/routers" + "code.gitea.io/gitea/tests" + + "gitea.com/go-chi/session" + "github.com/stretchr/testify/assert" +) + +func getSessionID(t *testing.T, resp *httptest.ResponseRecorder) string { + cookies := resp.Result().Cookies() + found := false + sessionID := "" + for _, cookie := range cookies { + if cookie.Name == setting.SessionConfig.CookieName { + sessionID = cookie.Value + found = true + } + } + assert.True(t, found) + assert.NotEmpty(t, sessionID) + return sessionID +} + +func sessionFile(tmpDir, sessionID string) string { + return filepath.Join(tmpDir, sessionID[0:1], sessionID[1:2], sessionID) +} + +func sessionFileExist(t *testing.T, tmpDir, sessionID string) bool { + sessionFile := sessionFile(tmpDir, sessionID) + _, err := os.Lstat(sessionFile) + if err != nil { + if os.IsNotExist(err) { + return false + } + assert.NoError(t, err) + } + return true +} + +func TestSessionFileCreation(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + oldSessionConfig := setting.SessionConfig.ProviderConfig + defer func() { + setting.SessionConfig.ProviderConfig = oldSessionConfig + c = routers.NormalRoutes(context.TODO()) + }() + + var config session.Options + + err := json.Unmarshal([]byte(oldSessionConfig), &config) + assert.NoError(t, err) + + config.Provider = "file" + + // Now create a temporaryDirectory + tmpDir, err := os.MkdirTemp("", "sessions") + assert.NoError(t, err) + defer func() { + if _, err := os.Stat(tmpDir); !os.IsNotExist(err) { + _ = util.RemoveAll(tmpDir) + } + }() + config.ProviderConfig = tmpDir + + newConfigBytes, err := json.Marshal(config) + assert.NoError(t, err) + + setting.SessionConfig.ProviderConfig = string(newConfigBytes) + + c = routers.NormalRoutes(context.TODO()) + + t.Run("NoSessionOnViewIssue", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user2/repo1/issues/1") + resp := MakeRequest(t, req, http.StatusOK) + sessionID := getSessionID(t, resp) + + // We're not logged in so there should be no session + assert.False(t, sessionFileExist(t, tmpDir, sessionID)) + }) + t.Run("CreateSessionOnLogin", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user/login") + resp := MakeRequest(t, req, http.StatusOK) + sessionID := getSessionID(t, resp) + + // We're not logged in so there should be no session + assert.False(t, sessionFileExist(t, tmpDir, sessionID)) + + doc := NewHTMLParser(t, resp.Body) + req = NewRequestWithValues(t, "POST", "/user/login", map[string]string{ + "_csrf": doc.GetCSRF(), + "user_name": "user2", + "password": userPassword, + }) + resp = MakeRequest(t, req, http.StatusSeeOther) + sessionID = getSessionID(t, resp) + + assert.FileExists(t, sessionFile(tmpDir, sessionID)) + }) +} diff --git a/tests/integration/csrf_test.go b/tests/integration/csrf_test.go new file mode 100644 index 0000000000..18a157412b --- /dev/null +++ b/tests/integration/csrf_test.go @@ -0,0 +1,53 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "strings" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestCsrfProtection(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // test web form csrf via form + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user.Name) + req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ + "_csrf": "fake_csrf", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + resp := session.MakeRequest(t, req, http.StatusSeeOther) + loc := resp.Header().Get("Location") + assert.Equal(t, setting.AppSubURL+"/", loc) + resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + assert.Equal(t, "Bad Request: invalid CSRF token", + strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()), + ) + + // test web form csrf via header. TODO: should use an UI api to test + req = NewRequest(t, "POST", "/user/settings") + req.Header.Add("X-Csrf-Token", "fake_csrf") + session.MakeRequest(t, req, http.StatusSeeOther) + + resp = session.MakeRequest(t, req, http.StatusSeeOther) + loc = resp.Header().Get("Location") + assert.Equal(t, setting.AppSubURL+"/", loc) + resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + assert.Equal(t, "Bad Request: invalid CSRF token", + strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()), + ) +} diff --git a/tests/integration/delete_user_test.go b/tests/integration/delete_user_test.go new file mode 100644 index 0000000000..1d9d257f12 --- /dev/null +++ b/tests/integration/delete_user_test.go @@ -0,0 +1,61 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/organization" + access_model "code.gitea.io/gitea/models/perm/access" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/tests" +) + +func assertUserDeleted(t *testing.T, userID int64) { + unittest.AssertNotExistsBean(t, &user_model.User{ID: userID}) + unittest.AssertNotExistsBean(t, &user_model.Follow{UserID: userID}) + unittest.AssertNotExistsBean(t, &user_model.Follow{FollowID: userID}) + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerID: userID}) + unittest.AssertNotExistsBean(t, &access_model.Access{UserID: userID}) + unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: userID}) + unittest.AssertNotExistsBean(t, &issues_model.IssueUser{UID: userID}) + unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: userID}) + unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID}) +} + +func TestUserDeleteAccount(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user8") + csrf := GetCSRF(t, session, "/user/settings/account") + urlStr := fmt.Sprintf("/user/settings/account/delete?password=%s", userPassword) + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "_csrf": csrf, + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + assertUserDeleted(t, 8) + unittest.CheckConsistencyFor(t, &user_model.User{}) +} + +func TestUserDeleteAccountStillOwnRepos(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + csrf := GetCSRF(t, session, "/user/settings/account") + urlStr := fmt.Sprintf("/user/settings/account/delete?password=%s", userPassword) + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "_csrf": csrf, + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // user should not have been deleted, because the user still owns repos + unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) +} diff --git a/tests/integration/download_test.go b/tests/integration/download_test.go new file mode 100644 index 0000000000..9d3b17d103 --- /dev/null +++ b/tests/integration/download_test.go @@ -0,0 +1,94 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestDownloadByID(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + + // Request raw blob + req := NewRequest(t, "GET", "/user2/repo1/raw/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f") + resp := session.MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String()) +} + +func TestDownloadByIDForSVGUsesSecureHeaders(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + + // Request raw blob + req := NewRequest(t, "GET", "/user2/repo2/raw/blob/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b") + resp := session.MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, "default-src 'none'; style-src 'unsafe-inline'; sandbox", resp.HeaderMap.Get("Content-Security-Policy")) + assert.Equal(t, "image/svg+xml", resp.HeaderMap.Get("Content-Type")) + assert.Equal(t, "nosniff", resp.HeaderMap.Get("X-Content-Type-Options")) +} + +func TestDownloadByIDMedia(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + + // Request raw blob + req := NewRequest(t, "GET", "/user2/repo1/media/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f") + resp := session.MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String()) +} + +func TestDownloadByIDMediaForSVGUsesSecureHeaders(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + + // Request raw blob + req := NewRequest(t, "GET", "/user2/repo2/media/blob/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b") + resp := session.MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, "default-src 'none'; style-src 'unsafe-inline'; sandbox", resp.HeaderMap.Get("Content-Security-Policy")) + assert.Equal(t, "image/svg+xml", resp.HeaderMap.Get("Content-Type")) + assert.Equal(t, "nosniff", resp.HeaderMap.Get("X-Content-Type-Options")) +} + +func TestDownloadRawTextFileWithoutMimeTypeMapping(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + + req := NewRequest(t, "GET", "/user2/repo2/raw/branch/master/test.xml") + resp := session.MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, "text/plain; charset=utf-8", resp.HeaderMap.Get("Content-Type")) +} + +func TestDownloadRawTextFileWithMimeTypeMapping(t *testing.T) { + defer tests.PrepareTestEnv(t)() + setting.MimeTypeMap.Map[".xml"] = "text/xml" + setting.MimeTypeMap.Enabled = true + + session := loginUser(t, "user2") + + req := NewRequest(t, "GET", "/user2/repo2/raw/branch/master/test.xml") + resp := session.MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, "text/xml; charset=utf-8", resp.HeaderMap.Get("Content-Type")) + + delete(setting.MimeTypeMap.Map, ".xml") + setting.MimeTypeMap.Enabled = false +} diff --git a/tests/integration/dump_restore_test.go b/tests/integration/dump_restore_test.go new file mode 100644 index 0000000000..19513d0271 --- /dev/null +++ b/tests/integration/dump_restore_test.go @@ -0,0 +1,328 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "context" + "errors" + "fmt" + "net/url" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + base "code.gitea.io/gitea/modules/migration" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/migrations" + + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" +) + +func TestDumpRestore(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + AllowLocalNetworks := setting.Migrations.AllowLocalNetworks + setting.Migrations.AllowLocalNetworks = true + AppVer := setting.AppVer + // Gitea SDK (go-sdk) need to parse the AppVer from server response, so we must set it to a valid version string. + setting.AppVer = "1.16.0" + defer func() { + setting.Migrations.AllowLocalNetworks = AllowLocalNetworks + setting.AppVer = AppVer + }() + + assert.NoError(t, migrations.Init()) + + reponame := "repo1" + + basePath, err := os.MkdirTemp("", reponame) + assert.NoError(t, err) + defer util.RemoveAll(basePath) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, repoOwner.Name) + token := getTokenForLoggedInUser(t, session) + + // + // Phase 1: dump repo1 from the Gitea instance to the filesystem + // + + ctx := context.Background() + opts := migrations.MigrateOptions{ + GitServiceType: structs.GiteaService, + Issues: true, + PullRequests: true, + Labels: true, + Milestones: true, + Comments: true, + AuthToken: token, + CloneAddr: repo.CloneLink().HTTPS, + RepoName: reponame, + } + err = migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts) + assert.NoError(t, err) + + // + // Verify desired side effects of the dump + // + d := filepath.Join(basePath, repo.OwnerName, repo.Name) + for _, f := range []string{"repo.yml", "topic.yml", "label.yml", "milestone.yml", "issue.yml"} { + assert.FileExists(t, filepath.Join(d, f)) + } + + // + // Phase 2: restore from the filesystem to the Gitea instance in restoredrepo + // + + newreponame := "restored" + err = migrations.RestoreRepository(ctx, d, repo.OwnerName, newreponame, []string{ + "labels", "issues", "comments", "milestones", "pull_requests", + }, false) + assert.NoError(t, err) + + newrepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: newreponame}) + + // + // Phase 3: dump restored from the Gitea instance to the filesystem + // + opts.RepoName = newreponame + opts.CloneAddr = newrepo.CloneLink().HTTPS + err = migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts) + assert.NoError(t, err) + + // + // Verify the dump of restored is the same as the dump of repo1 + // + comparator := &compareDump{ + t: t, + basePath: basePath, + } + comparator.assertEquals(repo, newrepo) + }) +} + +type compareDump struct { + t *testing.T + basePath string + repoBefore *repo_model.Repository + dirBefore string + repoAfter *repo_model.Repository + dirAfter string +} + +type compareField struct { + before interface{} + after interface{} + ignore bool + transform func(string) string + nested *compareFields +} + +type compareFields map[string]compareField + +func (c *compareDump) replaceRepoName(original string) string { + return strings.ReplaceAll(original, c.repoBefore.Name, c.repoAfter.Name) +} + +func (c *compareDump) assertEquals(repoBefore, repoAfter *repo_model.Repository) { + c.repoBefore = repoBefore + c.dirBefore = filepath.Join(c.basePath, repoBefore.OwnerName, repoBefore.Name) + c.repoAfter = repoAfter + c.dirAfter = filepath.Join(c.basePath, repoAfter.OwnerName, repoAfter.Name) + + // + // base.Repository + // + _ = c.assertEqual("repo.yml", base.Repository{}, compareFields{ + "Name": { + before: c.repoBefore.Name, + after: c.repoAfter.Name, + }, + "CloneURL": {transform: c.replaceRepoName}, + "OriginalURL": {transform: c.replaceRepoName}, + }) + + // + // base.Label + // + labels, ok := c.assertEqual("label.yml", []base.Label{}, compareFields{}).([]*base.Label) + assert.True(c.t, ok) + assert.GreaterOrEqual(c.t, len(labels), 1) + + // + // base.Milestone + // + milestones, ok := c.assertEqual("milestone.yml", []base.Milestone{}, compareFields{ + "Updated": {ignore: true}, // the database updates that field independently + }).([]*base.Milestone) + assert.True(c.t, ok) + assert.GreaterOrEqual(c.t, len(milestones), 1) + + // + // base.Issue and the associated comments + // + issues, ok := c.assertEqual("issue.yml", []base.Issue{}, compareFields{ + "Assignees": {ignore: true}, // not implemented yet + }).([]*base.Issue) + assert.True(c.t, ok) + assert.GreaterOrEqual(c.t, len(issues), 1) + for _, issue := range issues { + filename := filepath.Join("comments", fmt.Sprintf("%d.yml", issue.Number)) + comments, ok := c.assertEqual(filename, []base.Comment{}, compareFields{ + "Index": {ignore: true}, + }).([]*base.Comment) + assert.True(c.t, ok) + for _, comment := range comments { + assert.EqualValues(c.t, issue.Number, comment.IssueIndex) + } + } + + // + // base.PullRequest and the associated comments + // + comparePullRequestBranch := &compareFields{ + "RepoName": { + before: c.repoBefore.Name, + after: c.repoAfter.Name, + }, + "CloneURL": {transform: c.replaceRepoName}, + } + prs, ok := c.assertEqual("pull_request.yml", []base.PullRequest{}, compareFields{ + "Assignees": {ignore: true}, // not implemented yet + "Head": {nested: comparePullRequestBranch}, + "Base": {nested: comparePullRequestBranch}, + "Labels": {ignore: true}, // because org labels are not handled properly + }).([]*base.PullRequest) + assert.True(c.t, ok) + assert.GreaterOrEqual(c.t, len(prs), 1) + for _, pr := range prs { + filename := filepath.Join("comments", fmt.Sprintf("%d.yml", pr.Number)) + comments, ok := c.assertEqual(filename, []base.Comment{}, compareFields{}).([]*base.Comment) + assert.True(c.t, ok) + for _, comment := range comments { + assert.EqualValues(c.t, pr.Number, comment.IssueIndex) + } + } +} + +func (c *compareDump) assertLoadYAMLFiles(beforeFilename, afterFilename string, before, after interface{}) { + _, beforeErr := os.Stat(beforeFilename) + _, afterErr := os.Stat(afterFilename) + assert.EqualValues(c.t, errors.Is(beforeErr, os.ErrNotExist), errors.Is(afterErr, os.ErrNotExist)) + if errors.Is(beforeErr, os.ErrNotExist) { + return + } + + beforeBytes, err := os.ReadFile(beforeFilename) + assert.NoError(c.t, err) + assert.NoError(c.t, yaml.Unmarshal(beforeBytes, before)) + afterBytes, err := os.ReadFile(afterFilename) + assert.NoError(c.t, err) + assert.NoError(c.t, yaml.Unmarshal(afterBytes, after)) +} + +func (c *compareDump) assertLoadFiles(beforeFilename, afterFilename string, t reflect.Type) (before, after reflect.Value) { + var beforePtr, afterPtr reflect.Value + if t.Kind() == reflect.Slice { + // + // Given []Something{} create afterPtr, beforePtr []*Something{} + // + sliceType := reflect.SliceOf(reflect.PtrTo(t.Elem())) + beforeSlice := reflect.MakeSlice(sliceType, 0, 10) + beforePtr = reflect.New(beforeSlice.Type()) + beforePtr.Elem().Set(beforeSlice) + afterSlice := reflect.MakeSlice(sliceType, 0, 10) + afterPtr = reflect.New(afterSlice.Type()) + afterPtr.Elem().Set(afterSlice) + } else { + // + // Given Something{} create afterPtr, beforePtr *Something{} + // + beforePtr = reflect.New(t) + afterPtr = reflect.New(t) + } + c.assertLoadYAMLFiles(beforeFilename, afterFilename, beforePtr.Interface(), afterPtr.Interface()) + return beforePtr.Elem(), afterPtr.Elem() +} + +func (c *compareDump) assertEqual(filename string, kind interface{}, fields compareFields) (i interface{}) { + beforeFilename := filepath.Join(c.dirBefore, filename) + afterFilename := filepath.Join(c.dirAfter, filename) + + typeOf := reflect.TypeOf(kind) + before, after := c.assertLoadFiles(beforeFilename, afterFilename, typeOf) + if typeOf.Kind() == reflect.Slice { + i = c.assertEqualSlices(before, after, fields) + } else { + i = c.assertEqualValues(before, after, fields) + } + return i +} + +func (c *compareDump) assertEqualSlices(before, after reflect.Value, fields compareFields) interface{} { + assert.EqualValues(c.t, before.Len(), after.Len()) + if before.Len() == after.Len() { + for i := 0; i < before.Len(); i++ { + _ = c.assertEqualValues( + reflect.Indirect(before.Index(i).Elem()), + reflect.Indirect(after.Index(i).Elem()), + fields) + } + } + return after.Interface() +} + +func (c *compareDump) assertEqualValues(before, after reflect.Value, fields compareFields) interface{} { + for _, field := range reflect.VisibleFields(before.Type()) { + bf := before.FieldByName(field.Name) + bi := bf.Interface() + af := after.FieldByName(field.Name) + ai := af.Interface() + if compare, ok := fields[field.Name]; ok { + if compare.ignore == true { + // + // Ignore + // + continue + } + if compare.transform != nil { + // + // Transform these strings before comparing them + // + bs, ok := bi.(string) + assert.True(c.t, ok) + as, ok := ai.(string) + assert.True(c.t, ok) + assert.EqualValues(c.t, compare.transform(bs), compare.transform(as)) + continue + } + if compare.before != nil && compare.after != nil { + // + // The fields are expected to have different values + // + assert.EqualValues(c.t, compare.before, bi) + assert.EqualValues(c.t, compare.after, ai) + continue + } + if compare.nested != nil { + // + // The fields are a struct, recurse + // + c.assertEqualValues(bf, af, *compare.nested) + continue + } + } + assert.EqualValues(c.t, bi, ai) + } + return after.Interface() +} diff --git a/tests/integration/editor_test.go b/tests/integration/editor_test.go new file mode 100644 index 0000000000..19e80dc7bf --- /dev/null +++ b/tests/integration/editor_test.go @@ -0,0 +1,164 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/http/httptest" + "net/url" + "path" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCreateFile(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user2") + + // Request editor page + req := NewRequest(t, "GET", "/user2/repo1/_new/master/") + resp := session.MakeRequest(t, req, http.StatusOK) + + doc := NewHTMLParser(t, resp.Body) + lastCommit := doc.GetInputValueByName("last_commit") + assert.NotEmpty(t, lastCommit) + + // Save new file to master branch + req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{ + "_csrf": doc.GetCSRF(), + "last_commit": lastCommit, + "tree_path": "test.txt", + "content": "Content", + "commit_choice": "direct", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + }) +} + +func TestCreateFileOnProtectedBranch(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user2") + + csrf := GetCSRF(t, session, "/user2/repo1/settings/branches") + // Change master branch to protected + req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/master", map[string]string{ + "_csrf": csrf, + "protected": "on", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + // Check if master branch has been locked successfully + flashCookie := session.GetCookie("macaron_flash") + assert.NotNil(t, flashCookie) + assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) + + // Request editor page + req = NewRequest(t, "GET", "/user2/repo1/_new/master/") + resp := session.MakeRequest(t, req, http.StatusOK) + + doc := NewHTMLParser(t, resp.Body) + lastCommit := doc.GetInputValueByName("last_commit") + assert.NotEmpty(t, lastCommit) + + // Save new file to master branch + req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{ + "_csrf": doc.GetCSRF(), + "last_commit": lastCommit, + "tree_path": "test.txt", + "content": "Content", + "commit_choice": "direct", + }) + + resp = session.MakeRequest(t, req, http.StatusOK) + // Check body for error message + assert.Contains(t, resp.Body.String(), "Cannot commit to protected branch 'master'.") + + // remove the protected branch + csrf = GetCSRF(t, session, "/user2/repo1/settings/branches") + // Change master branch to protected + req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/master", map[string]string{ + "_csrf": csrf, + "protected": "off", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + // Check if master branch has been locked successfully + flashCookie = session.GetCookie("macaron_flash") + assert.NotNil(t, flashCookie) + assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bdisabled.", flashCookie.Value) + }) +} + +func testEditFile(t *testing.T, session *TestSession, user, repo, branch, filePath, newContent string) *httptest.ResponseRecorder { + // Get to the 'edit this file' page + req := NewRequest(t, "GET", path.Join(user, repo, "_edit", branch, filePath)) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + lastCommit := htmlDoc.GetInputValueByName("last_commit") + assert.NotEmpty(t, lastCommit) + + // Submit the edits + req = NewRequestWithValues(t, "POST", path.Join(user, repo, "_edit", branch, filePath), + map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "last_commit": lastCommit, + "tree_path": filePath, + "content": newContent, + "commit_choice": "direct", + }, + ) + session.MakeRequest(t, req, http.StatusSeeOther) + + // Verify the change + req = NewRequest(t, "GET", path.Join(user, repo, "raw/branch", branch, filePath)) + resp = session.MakeRequest(t, req, http.StatusOK) + assert.EqualValues(t, newContent, resp.Body.String()) + + return resp +} + +func testEditFileToNewBranch(t *testing.T, session *TestSession, user, repo, branch, targetBranch, filePath, newContent string) *httptest.ResponseRecorder { + // Get to the 'edit this file' page + req := NewRequest(t, "GET", path.Join(user, repo, "_edit", branch, filePath)) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + lastCommit := htmlDoc.GetInputValueByName("last_commit") + assert.NotEmpty(t, lastCommit) + + // Submit the edits + req = NewRequestWithValues(t, "POST", path.Join(user, repo, "_edit", branch, filePath), + map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "last_commit": lastCommit, + "tree_path": filePath, + "content": newContent, + "commit_choice": "commit-to-new-branch", + "new_branch_name": targetBranch, + }, + ) + session.MakeRequest(t, req, http.StatusSeeOther) + + // Verify the change + req = NewRequest(t, "GET", path.Join(user, repo, "raw/branch", targetBranch, filePath)) + resp = session.MakeRequest(t, req, http.StatusOK) + assert.EqualValues(t, newContent, resp.Body.String()) + + return resp +} + +func TestEditFile(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user2") + testEditFile(t, session, "user2", "repo1", "master", "README.md", "Hello, World (Edited)\n") + }) +} + +func TestEditFileToNewBranch(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user2") + testEditFileToNewBranch(t, session, "user2", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n") + }) +} diff --git a/tests/integration/empty_repo_test.go b/tests/integration/empty_repo_test.go new file mode 100644 index 0000000000..8810363dc8 --- /dev/null +++ b/tests/integration/empty_repo_test.go @@ -0,0 +1,31 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/tests" +) + +func TestEmptyRepo(t *testing.T) { + defer tests.PrepareTestEnv(t)() + subpaths := []string{ + "commits/master", + "raw/foo", + "commit/1ae57b34ccf7e18373", + "graph", + } + emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{}, unittest.Cond("is_empty = ?", true)) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: emptyRepo.OwnerID}) + for _, subpath := range subpaths { + req := NewRequestf(t, "GET", "/%s/%s/%s", owner.Name, emptyRepo.Name, subpath) + MakeRequest(t, req, http.StatusNotFound) + } +} diff --git a/tests/integration/eventsource_test.go b/tests/integration/eventsource_test.go new file mode 100644 index 0000000000..cd496e0129 --- /dev/null +++ b/tests/integration/eventsource_test.go @@ -0,0 +1,83 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + "time" + + activities_model "code.gitea.io/gitea/models/activities" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/eventsource" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestEventSourceManagerRun(t *testing.T) { + defer tests.PrepareTestEnv(t)() + manager := eventsource.GetManager() + + eventChan := manager.Register(2) + defer func() { + manager.Unregister(2, eventChan) + // ensure the eventChan is closed + for { + _, ok := <-eventChan + if !ok { + break + } + } + }() + expectNotificationCountEvent := func(count int64) func() bool { + return func() bool { + select { + case event, ok := <-eventChan: + if !ok { + return false + } + data, ok := event.Data.(activities_model.UserIDCount) + if !ok { + return false + } + return event.Name == "notification-count" && data.Count == count + default: + return false + } + } + } + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + thread5 := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5}) + assert.NoError(t, thread5.LoadAttributes()) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session) + + var apiNL []api.NotificationThread + + // -- mark notifications as read -- + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token)) + resp := session.MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &apiNL) + assert.Len(t, apiNL, 2) + + lastReadAt := "2000-01-01T00%3A50%3A01%2B00%3A00" // 946687801 <- only Notification 4 is in this filter ... + req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token)) + session.MakeRequest(t, req, http.StatusResetContent) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s&status-types=unread", token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiNL) + assert.Len(t, apiNL, 1) + + assert.Eventually(t, expectNotificationCountEvent(1), 30*time.Second, 1*time.Second) +} diff --git a/tests/integration/explore_repos_test.go b/tests/integration/explore_repos_test.go new file mode 100644 index 0000000000..dca3252753 --- /dev/null +++ b/tests/integration/explore_repos_test.go @@ -0,0 +1,19 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/tests" +) + +func TestExploreRepos(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/explore/repos") + MakeRequest(t, req, http.StatusOK) +} diff --git a/tests/integration/git_clone_wiki_test.go b/tests/integration/git_clone_wiki_test.go new file mode 100644 index 0000000000..4bdbc9b7c3 --- /dev/null +++ b/tests/integration/git_clone_wiki_test.go @@ -0,0 +1,53 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "context" + "fmt" + "net/url" + "os" + "path/filepath" + "testing" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func assertFileExist(t *testing.T, p string) { + exist, err := util.IsExist(p) + assert.NoError(t, err) + assert.True(t, exist) +} + +func assertFileEqual(t *testing.T, p string, content []byte) { + bs, err := os.ReadFile(p) + assert.NoError(t, err) + assert.EqualValues(t, content, bs) +} + +func TestRepoCloneWiki(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + defer tests.PrepareTestEnv(t)() + + dstPath, err := os.MkdirTemp("", "clone_wiki") + assert.NoError(t, err) + + r := fmt.Sprintf("%suser2/repo1.wiki.git", u.String()) + u, _ = url.Parse(r) + u.User = url.UserPassword("user2", userPassword) + t.Run("Clone", func(t *testing.T) { + assert.NoError(t, git.CloneWithArgs(context.Background(), u.String(), dstPath, git.AllowLFSFiltersArgs(), git.CloneRepoOptions{})) + assertFileEqual(t, filepath.Join(dstPath, "Home.md"), []byte("# Home page\n\nThis is the home page!\n")) + assertFileExist(t, filepath.Join(dstPath, "Page-With-Image.md")) + assertFileExist(t, filepath.Join(dstPath, "Page-With-Spaced-Name.md")) + assertFileExist(t, filepath.Join(dstPath, "images")) + assertFileExist(t, filepath.Join(dstPath, "jpeg.jpg")) + }) + }) +} diff --git a/tests/integration/git_helper_for_declarative_test.go b/tests/integration/git_helper_for_declarative_test.go new file mode 100644 index 0000000000..666f9f6fe9 --- /dev/null +++ b/tests/integration/git_helper_for_declarative_test.go @@ -0,0 +1,202 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "context" + "fmt" + "net" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "strconv" + "testing" + "time" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/ssh" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func withKeyFile(t *testing.T, keyname string, callback func(string)) { + tmpDir, err := os.MkdirTemp("", "key-file") + assert.NoError(t, err) + defer util.RemoveAll(tmpDir) + + err = os.Chmod(tmpDir, 0o700) + assert.NoError(t, err) + + keyFile := filepath.Join(tmpDir, keyname) + err = ssh.GenKeyPair(keyFile) + assert.NoError(t, err) + + err = os.WriteFile(path.Join(tmpDir, "ssh"), []byte("#!/bin/bash\n"+ + "ssh -o \"UserKnownHostsFile=/dev/null\" -o \"StrictHostKeyChecking=no\" -o \"IdentitiesOnly=yes\" -i \""+keyFile+"\" \"$@\""), 0o700) + assert.NoError(t, err) + + // Setup ssh wrapper + os.Setenv("GIT_SSH", path.Join(tmpDir, "ssh")) + os.Setenv("GIT_SSH_COMMAND", + "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i \""+keyFile+"\"") + os.Setenv("GIT_SSH_VARIANT", "ssh") + + callback(keyFile) +} + +func createSSHUrl(gitPath string, u *url.URL) *url.URL { + u2 := *u + u2.Scheme = "ssh" + u2.User = url.User("git") + u2.Host = net.JoinHostPort(setting.SSH.ListenHost, strconv.Itoa(setting.SSH.ListenPort)) + u2.Path = gitPath + return &u2 +} + +func onGiteaRunTB(t testing.TB, callback func(testing.TB, *url.URL), prepare ...bool) { + if len(prepare) == 0 || prepare[0] { + defer tests.PrepareTestEnv(t, 1)() + } + s := http.Server{ + Handler: c, + } + + u, err := url.Parse(setting.AppURL) + assert.NoError(t, err) + listener, err := net.Listen("tcp", u.Host) + i := 0 + for err != nil && i <= 10 { + time.Sleep(100 * time.Millisecond) + listener, err = net.Listen("tcp", u.Host) + i++ + } + assert.NoError(t, err) + u.Host = listener.Addr().String() + + defer func() { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + s.Shutdown(ctx) + cancel() + }() + + go s.Serve(listener) + // Started by config go ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) + + callback(t, u) +} + +func onGiteaRun(t *testing.T, callback func(*testing.T, *url.URL), prepare ...bool) { + onGiteaRunTB(t, func(t testing.TB, u *url.URL) { + callback(t.(*testing.T), u) + }, prepare...) +} + +func doGitClone(dstLocalPath string, u *url.URL) func(*testing.T) { + return func(t *testing.T) { + assert.NoError(t, git.CloneWithArgs(context.Background(), u.String(), dstLocalPath, git.AllowLFSFiltersArgs(), git.CloneRepoOptions{})) + exist, err := util.IsExist(filepath.Join(dstLocalPath, "README.md")) + assert.NoError(t, err) + assert.True(t, exist) + } +} + +func doPartialGitClone(dstLocalPath string, u *url.URL) func(*testing.T) { + return func(t *testing.T) { + assert.NoError(t, git.CloneWithArgs(context.Background(), u.String(), dstLocalPath, git.AllowLFSFiltersArgs(), git.CloneRepoOptions{ + Filter: "blob:none", + })) + exist, err := util.IsExist(filepath.Join(dstLocalPath, "README.md")) + assert.NoError(t, err) + assert.True(t, exist) + } +} + +func doGitCloneFail(u *url.URL) func(*testing.T) { + return func(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "doGitCloneFail") + assert.NoError(t, err) + defer util.RemoveAll(tmpDir) + assert.Error(t, git.Clone(git.DefaultContext, u.String(), tmpDir, git.CloneRepoOptions{})) + exist, err := util.IsExist(filepath.Join(tmpDir, "README.md")) + assert.NoError(t, err) + assert.False(t, exist) + } +} + +func doGitInitTestRepository(dstPath string) func(*testing.T) { + return func(t *testing.T) { + // Init repository in dstPath + assert.NoError(t, git.InitRepository(git.DefaultContext, dstPath, false)) + // forcibly set default branch to master + _, _, err := git.NewCommand(git.DefaultContext, "symbolic-ref", "HEAD", git.BranchPrefix+"master").RunStdString(&git.RunOpts{Dir: dstPath}) + assert.NoError(t, err) + assert.NoError(t, os.WriteFile(filepath.Join(dstPath, "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", dstPath)), 0o644)) + assert.NoError(t, git.AddChanges(dstPath, true)) + signature := git.Signature{ + Email: "test@example.com", + Name: "test", + When: time.Now(), + } + assert.NoError(t, git.CommitChanges(dstPath, git.CommitChangesOptions{ + Committer: &signature, + Author: &signature, + Message: "Initial Commit", + })) + } +} + +func doGitAddRemote(dstPath, remoteName string, u *url.URL) func(*testing.T) { + return func(t *testing.T) { + _, _, err := git.NewCommand(git.DefaultContext, "remote", "add", remoteName, u.String()).RunStdString(&git.RunOpts{Dir: dstPath}) + assert.NoError(t, err) + } +} + +func doGitPushTestRepository(dstPath string, args ...string) func(*testing.T) { + return func(t *testing.T) { + _, _, err := git.NewCommand(git.DefaultContext, append([]string{"push", "-u"}, args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) + assert.NoError(t, err) + } +} + +func doGitPushTestRepositoryFail(dstPath string, args ...string) func(*testing.T) { + return func(t *testing.T) { + _, _, err := git.NewCommand(git.DefaultContext, append([]string{"push"}, args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) + assert.Error(t, err) + } +} + +func doGitCreateBranch(dstPath, branch string) func(*testing.T) { + return func(t *testing.T) { + _, _, err := git.NewCommand(git.DefaultContext, "checkout", "-b", branch).RunStdString(&git.RunOpts{Dir: dstPath}) + assert.NoError(t, err) + } +} + +func doGitCheckoutBranch(dstPath string, args ...string) func(*testing.T) { + return func(t *testing.T) { + _, _, err := git.NewCommandNoGlobals(append(append(git.AllowLFSFiltersArgs(), "checkout"), args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) + assert.NoError(t, err) + } +} + +func doGitMerge(dstPath string, args ...string) func(*testing.T) { + return func(t *testing.T) { + _, _, err := git.NewCommand(git.DefaultContext, append([]string{"merge"}, args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) + assert.NoError(t, err) + } +} + +func doGitPull(dstPath string, args ...string) func(*testing.T) { + return func(t *testing.T) { + _, _, err := git.NewCommandNoGlobals(append(append(git.AllowLFSFiltersArgs(), "pull"), args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) + assert.NoError(t, err) + } +} diff --git a/tests/integration/git_smart_http_test.go b/tests/integration/git_smart_http_test.go new file mode 100644 index 0000000000..02b0e93870 --- /dev/null +++ b/tests/integration/git_smart_http_test.go @@ -0,0 +1,69 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "io" + "net/http" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGitSmartHTTP(t *testing.T) { + onGiteaRun(t, testGitSmartHTTP) +} + +func testGitSmartHTTP(t *testing.T, u *url.URL) { + kases := []struct { + p string + code int + }{ + { + p: "user2/repo1/info/refs", + code: http.StatusOK, + }, + { + p: "user2/repo1/HEAD", + code: http.StatusOK, + }, + { + p: "user2/repo1/objects/info/alternates", + code: http.StatusNotFound, + }, + { + p: "user2/repo1/objects/info/http-alternates", + code: http.StatusNotFound, + }, + { + p: "user2/repo1/../../custom/conf/app.ini", + code: http.StatusNotFound, + }, + { + p: "user2/repo1/objects/info/../../../../custom/conf/app.ini", + code: http.StatusNotFound, + }, + { + p: `user2/repo1/objects/info/..\..\..\..\custom\conf\app.ini`, + code: http.StatusBadRequest, + }, + } + + for _, kase := range kases { + t.Run(kase.p, func(t *testing.T) { + p := u.String() + kase.p + req, err := http.NewRequest("GET", p, nil) + assert.NoError(t, err) + req.SetBasicAuth("user2", userPassword) + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + defer resp.Body.Close() + assert.EqualValues(t, kase.code, resp.StatusCode) + _, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + }) + } +} diff --git a/tests/integration/git_test.go b/tests/integration/git_test.go new file mode 100644 index 0000000000..caeb5db8b3 --- /dev/null +++ b/tests/integration/git_test.go @@ -0,0 +1,851 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "encoding/hex" + "fmt" + "math/rand" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "strconv" + "testing" + "time" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/perm" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/lfs" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +const ( + littleSize = 1024 // 1ko + bigSize = 128 * 1024 * 1024 // 128Mo +) + +func TestGit(t *testing.T) { + onGiteaRun(t, testGit) +} + +func testGit(t *testing.T, u *url.URL) { + username := "user2" + baseAPITestContext := NewAPITestContext(t, username, "repo1") + + u.Path = baseAPITestContext.GitPath() + + forkedUserCtx := NewAPITestContext(t, "user4", "repo1") + + t.Run("HTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + ensureAnonymousClone(t, u) + httpContext := baseAPITestContext + httpContext.Reponame = "repo-tmp-17" + forkedUserCtx.Reponame = httpContext.Reponame + + dstPath, err := os.MkdirTemp("", httpContext.Reponame) + assert.NoError(t, err) + defer util.RemoveAll(dstPath) + + t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false)) + t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, httpContext.Username, perm.AccessModeRead)) + + t.Run("ForkFromDifferentUser", doAPIForkRepository(httpContext, forkedUserCtx.Username)) + + u.Path = httpContext.GitPath() + u.User = url.UserPassword(username, userPassword) + + t.Run("Clone", doGitClone(dstPath, u)) + + dstPath2, err := os.MkdirTemp("", httpContext.Reponame) + assert.NoError(t, err) + defer util.RemoveAll(dstPath2) + + t.Run("Partial Clone", doPartialGitClone(dstPath2, u)) + + little, big := standardCommitAndPushTest(t, dstPath) + littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath) + rawTest(t, &httpContext, little, big, littleLFS, bigLFS) + mediaTest(t, &httpContext, little, big, littleLFS, bigLFS) + + t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &httpContext, "master", "test/head")) + t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath)) + t.Run("AutoMerge", doAutoPRMerge(&httpContext, dstPath)) + t.Run("CreatePRAndSetManuallyMerged", doCreatePRAndSetManuallyMerged(httpContext, httpContext, dstPath, "master", "test-manually-merge")) + t.Run("MergeFork", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + t.Run("CreatePRAndMerge", doMergeFork(httpContext, forkedUserCtx, "master", httpContext.Username+":master")) + rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) + mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) + }) + + t.Run("PushCreate", doPushCreate(httpContext, u)) + }) + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + sshContext := baseAPITestContext + sshContext.Reponame = "repo-tmp-18" + keyname := "my-testing-key" + forkedUserCtx.Reponame = sshContext.Reponame + t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false)) + t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, sshContext.Username, perm.AccessModeRead)) + t.Run("ForkFromDifferentUser", doAPIForkRepository(sshContext, forkedUserCtx.Username)) + + // Setup key the user ssh key + withKeyFile(t, keyname, func(keyFile string) { + t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile)) + + // Setup remote link + // TODO: get url from api + sshURL := createSSHUrl(sshContext.GitPath(), u) + + // Setup clone folder + dstPath, err := os.MkdirTemp("", sshContext.Reponame) + assert.NoError(t, err) + defer util.RemoveAll(dstPath) + + t.Run("Clone", doGitClone(dstPath, sshURL)) + + little, big := standardCommitAndPushTest(t, dstPath) + littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath) + rawTest(t, &sshContext, little, big, littleLFS, bigLFS) + mediaTest(t, &sshContext, little, big, littleLFS, bigLFS) + + t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &sshContext, "master", "test/head2")) + t.Run("BranchProtectMerge", doBranchProtectPRMerge(&sshContext, dstPath)) + t.Run("MergeFork", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + t.Run("CreatePRAndMerge", doMergeFork(sshContext, forkedUserCtx, "master", sshContext.Username+":master")) + rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) + mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) + }) + + t.Run("PushCreate", doPushCreate(sshContext, sshURL)) + }) + }) +} + +func ensureAnonymousClone(t *testing.T, u *url.URL) { + dstLocalPath, err := os.MkdirTemp("", "repo1") + assert.NoError(t, err) + defer util.RemoveAll(dstLocalPath) + t.Run("CloneAnonymous", doGitClone(dstLocalPath, u)) +} + +func standardCommitAndPushTest(t *testing.T, dstPath string) (little, big string) { + t.Run("Standard", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + little, big = commitAndPushTest(t, dstPath, "data-file-") + }) + return little, big +} + +func lfsCommitAndPushTest(t *testing.T, dstPath string) (littleLFS, bigLFS string) { + t.Run("LFS", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + prefix := "lfs-data-file-" + err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("install").Run(&git.RunOpts{Dir: dstPath}) + assert.NoError(t, err) + _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("track", prefix+"*").RunStdString(&git.RunOpts{Dir: dstPath}) + assert.NoError(t, err) + err = git.AddChanges(dstPath, false, ".gitattributes") + assert.NoError(t, err) + + err = git.CommitChangesWithArgs(dstPath, git.AllowLFSFiltersArgs(), git.CommitChangesOptions{ + Committer: &git.Signature{ + Email: "user2@example.com", + Name: "User Two", + When: time.Now(), + }, + Author: &git.Signature{ + Email: "user2@example.com", + Name: "User Two", + When: time.Now(), + }, + Message: fmt.Sprintf("Testing commit @ %v", time.Now()), + }) + assert.NoError(t, err) + + littleLFS, bigLFS = commitAndPushTest(t, dstPath, prefix) + + t.Run("Locks", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + lockTest(t, dstPath) + }) + }) + return littleLFS, bigLFS +} + +func commitAndPushTest(t *testing.T, dstPath, prefix string) (little, big string) { + t.Run("PushCommit", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + t.Run("Little", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + little = doCommitAndPush(t, littleSize, dstPath, prefix) + }) + t.Run("Big", func(t *testing.T) { + if testing.Short() { + t.Skip("Skipping test in short mode.") + return + } + defer tests.PrintCurrentTest(t)() + big = doCommitAndPush(t, bigSize, dstPath, prefix) + }) + }) + return little, big +} + +func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS string) { + t.Run("Raw", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + username := ctx.Username + reponame := ctx.Reponame + + session := loginUser(t, username) + + // Request raw paths + req := NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", little)) + resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) + assert.Equal(t, littleSize, resp.Length) + + if setting.LFS.StartServer { + req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", littleLFS)) + resp := session.MakeRequest(t, req, http.StatusOK) + assert.NotEqual(t, littleSize, resp.Body.Len()) + assert.LessOrEqual(t, resp.Body.Len(), 1024) + if resp.Body.Len() != littleSize && resp.Body.Len() <= 1024 { + assert.Contains(t, resp.Body.String(), lfs.MetaFileIdentifier) + } + } + + if !testing.Short() { + req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", big)) + resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) + assert.Equal(t, bigSize, resp.Length) + + if setting.LFS.StartServer { + req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", bigLFS)) + resp := session.MakeRequest(t, req, http.StatusOK) + assert.NotEqual(t, bigSize, resp.Body.Len()) + if resp.Body.Len() != bigSize && resp.Body.Len() <= 1024 { + assert.Contains(t, resp.Body.String(), lfs.MetaFileIdentifier) + } + } + } + }) +} + +func mediaTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS string) { + t.Run("Media", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + username := ctx.Username + reponame := ctx.Reponame + + session := loginUser(t, username) + + // Request media paths + req := NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", little)) + resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) + assert.Equal(t, littleSize, resp.Length) + + req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", littleLFS)) + resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) + assert.Equal(t, littleSize, resp.Length) + + if !testing.Short() { + req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", big)) + resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) + assert.Equal(t, bigSize, resp.Length) + + if setting.LFS.StartServer { + req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", bigLFS)) + resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) + assert.Equal(t, bigSize, resp.Length) + } + } + }) +} + +func lockTest(t *testing.T, repoPath string) { + lockFileTest(t, "README.md", repoPath) +} + +func lockFileTest(t *testing.T, filename, repoPath string) { + _, _, err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath}) + assert.NoError(t, err) + _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("lock", filename).RunStdString(&git.RunOpts{Dir: repoPath}) + assert.NoError(t, err) + _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath}) + assert.NoError(t, err) + _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("unlock", filename).RunStdString(&git.RunOpts{Dir: repoPath}) + assert.NoError(t, err) +} + +func doCommitAndPush(t *testing.T, size int, repoPath, prefix string) string { + name, err := generateCommitWithNewData(size, repoPath, "user2@example.com", "User Two", prefix) + assert.NoError(t, err) + _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "master").RunStdString(&git.RunOpts{Dir: repoPath}) // Push + assert.NoError(t, err) + return name +} + +func generateCommitWithNewData(size int, repoPath, email, fullName, prefix string) (string, error) { + // Generate random file + bufSize := 4 * 1024 + if bufSize > size { + bufSize = size + } + + buffer := make([]byte, bufSize) + + tmpFile, err := os.CreateTemp(repoPath, prefix) + if err != nil { + return "", err + } + defer tmpFile.Close() + written := 0 + for written < size { + n := size - written + if n > bufSize { + n = bufSize + } + _, err := rand.Read(buffer[:n]) + if err != nil { + return "", err + } + n, err = tmpFile.Write(buffer[:n]) + if err != nil { + return "", err + } + written += n + } + if err != nil { + return "", err + } + + // Commit + // Now here we should explicitly allow lfs filters to run + globalArgs := git.AllowLFSFiltersArgs() + err = git.AddChangesWithArgs(repoPath, globalArgs, false, filepath.Base(tmpFile.Name())) + if err != nil { + return "", err + } + err = git.CommitChangesWithArgs(repoPath, globalArgs, git.CommitChangesOptions{ + Committer: &git.Signature{ + Email: email, + Name: fullName, + When: time.Now(), + }, + Author: &git.Signature{ + Email: email, + Name: fullName, + When: time.Now(), + }, + Message: fmt.Sprintf("Testing commit @ %v", time.Now()), + }) + return filepath.Base(tmpFile.Name()), err +} + +func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { + return func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + t.Run("CreateBranchProtected", doGitCreateBranch(dstPath, "protected")) + t.Run("PushProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected")) + + ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame) + t.Run("ProtectProtectedBranchNoWhitelist", doProtectBranch(ctx, "protected", "", "")) + t.Run("GenerateCommit", func(t *testing.T) { + _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-") + assert.NoError(t, err) + }) + t.Run("FailToPushToProtectedBranch", doGitPushTestRepositoryFail(dstPath, "origin", "protected")) + t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected")) + var pr api.PullRequest + var err error + t.Run("CreatePullRequest", func(t *testing.T) { + pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "protected", "unprotected")(t) + assert.NoError(t, err) + }) + t.Run("GenerateCommit", func(t *testing.T) { + _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-") + assert.NoError(t, err) + }) + t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected-2")) + var pr2 api.PullRequest + t.Run("CreatePullRequest", func(t *testing.T) { + pr2, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "unprotected", "unprotected-2")(t) + assert.NoError(t, err) + }) + t.Run("MergePR2", doAPIMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr2.Index)) + t.Run("MergePR", doAPIMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) + t.Run("PullProtected", doGitPull(dstPath, "origin", "protected")) + + t.Run("ProtectProtectedBranchUnprotectedFilePaths", doProtectBranch(ctx, "protected", "", "unprotected-file-*")) + t.Run("GenerateCommit", func(t *testing.T) { + _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "unprotected-file-") + assert.NoError(t, err) + }) + t.Run("PushUnprotectedFilesToProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected")) + + t.Run("ProtectProtectedBranchWhitelist", doProtectBranch(ctx, "protected", baseCtx.Username, "")) + + t.Run("CheckoutMaster", doGitCheckoutBranch(dstPath, "master")) + t.Run("CreateBranchForced", doGitCreateBranch(dstPath, "toforce")) + t.Run("GenerateCommit", func(t *testing.T) { + _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-") + assert.NoError(t, err) + }) + t.Run("FailToForcePushToProtectedBranch", doGitPushTestRepositoryFail(dstPath, "-f", "origin", "toforce:protected")) + t.Run("MergeProtectedToToforce", doGitMerge(dstPath, "protected")) + t.Run("PushToProtectedBranch", doGitPushTestRepository(dstPath, "origin", "toforce:protected")) + t.Run("CheckoutMasterAgain", doGitCheckoutBranch(dstPath, "master")) + } +} + +func doProtectBranch(ctx APITestContext, branch, userToWhitelist, unprotectedFilePatterns string) func(t *testing.T) { + // We are going to just use the owner to set the protection. + return func(t *testing.T) { + csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/settings/branches", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame))) + + if userToWhitelist == "" { + // Change branch to protected + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/%s", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), url.PathEscape(branch)), map[string]string{ + "_csrf": csrf, + "protected": "on", + "unprotected_file_patterns": unprotectedFilePatterns, + }) + ctx.Session.MakeRequest(t, req, http.StatusSeeOther) + } else { + user, err := user_model.GetUserByName(db.DefaultContext, userToWhitelist) + assert.NoError(t, err) + // Change branch to protected + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/%s", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), url.PathEscape(branch)), map[string]string{ + "_csrf": csrf, + "protected": "on", + "enable_push": "whitelist", + "enable_whitelist": "on", + "whitelist_users": strconv.FormatInt(user.ID, 10), + "unprotected_file_patterns": unprotectedFilePatterns, + }) + ctx.Session.MakeRequest(t, req, http.StatusSeeOther) + } + // Check if master branch has been locked successfully + flashCookie := ctx.Session.GetCookie("macaron_flash") + assert.NotNil(t, flashCookie) + assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527"+url.QueryEscape(branch)+"%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) + } +} + +func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) func(t *testing.T) { + return func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + var pr api.PullRequest + var err error + + // Create a test pullrequest + t.Run("CreatePullRequest", func(t *testing.T) { + pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t) + assert.NoError(t, err) + }) + + // Ensure the PR page works + t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) + + // Then get the diff string + var diffHash string + var diffLength int + t.Run("GetDiff", func(t *testing.T) { + req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(baseCtx.Username), url.PathEscape(baseCtx.Reponame), pr.Index)) + resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK) + diffHash = string(resp.Hash.Sum(nil)) + diffLength = resp.Length + }) + + // Now: Merge the PR & make sure that doesn't break the PR page or change its diff + t.Run("MergePR", doAPIMergePullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)) + t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) + t.Run("CheckPR", func(t *testing.T) { + oldMergeBase := pr.MergeBase + pr2, err := doAPIGetPullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) + assert.NoError(t, err) + assert.Equal(t, oldMergeBase, pr2.MergeBase) + }) + t.Run("EnsurDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) + + // Then: Delete the head branch & make sure that doesn't break the PR page or change its diff + t.Run("DeleteHeadBranch", doBranchDelete(baseCtx, baseCtx.Username, baseCtx.Reponame, headBranch)) + t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) + t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) + + // Delete the head repository & make sure that doesn't break the PR page or change its diff + t.Run("DeleteHeadRepository", doAPIDeleteRepository(ctx)) + t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) + t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) + } +} + +func doCreatePRAndSetManuallyMerged(ctx, baseCtx APITestContext, dstPath, baseBranch, headBranch string) func(t *testing.T) { + return func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + var ( + pr api.PullRequest + err error + lastCommitID string + ) + + trueBool := true + falseBool := false + + t.Run("AllowSetManuallyMergedAndSwitchOffAutodetectManualMerge", doAPIEditRepository(baseCtx, &api.EditRepoOption{ + HasPullRequests: &trueBool, + AllowManualMerge: &trueBool, + AutodetectManualMerge: &falseBool, + })) + + t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch)) + t.Run("PushToHeadBranch", doGitPushTestRepository(dstPath, "origin", headBranch)) + t.Run("CreateEmptyPullRequest", func(t *testing.T) { + pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t) + assert.NoError(t, err) + }) + lastCommitID = pr.Base.Sha + t.Run("ManuallyMergePR", doAPIManuallyMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, lastCommitID, pr.Index)) + } +} + +func doEnsureCanSeePull(ctx APITestContext, pr api.PullRequest) func(t *testing.T) { + return func(t *testing.T) { + req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) + ctx.Session.MakeRequest(t, req, http.StatusOK) + req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/files", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) + ctx.Session.MakeRequest(t, req, http.StatusOK) + req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/commits", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) + ctx.Session.MakeRequest(t, req, http.StatusOK) + } +} + +func doEnsureDiffNoChange(ctx APITestContext, pr api.PullRequest, diffHash string, diffLength int) func(t *testing.T) { + return func(t *testing.T) { + req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) + resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK) + actual := string(resp.Hash.Sum(nil)) + actualLength := resp.Length + + equal := diffHash == actual + assert.True(t, equal, "Unexpected change in the diff string: expected hash: %s size: %d but was actually: %s size: %d", hex.EncodeToString([]byte(diffHash)), diffLength, hex.EncodeToString([]byte(actual)), actualLength) + } +} + +func doPushCreate(ctx APITestContext, u *url.URL) func(t *testing.T) { + return func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // create a context for a currently non-existent repository + ctx.Reponame = fmt.Sprintf("repo-tmp-push-create-%s", u.Scheme) + u.Path = ctx.GitPath() + + // Create a temporary directory + tmpDir, err := os.MkdirTemp("", ctx.Reponame) + assert.NoError(t, err) + defer util.RemoveAll(tmpDir) + + // Now create local repository to push as our test and set its origin + t.Run("InitTestRepository", doGitInitTestRepository(tmpDir)) + t.Run("AddRemote", doGitAddRemote(tmpDir, "origin", u)) + + // Disable "Push To Create" and attempt to push + setting.Repository.EnablePushCreateUser = false + t.Run("FailToPushAndCreateTestRepository", doGitPushTestRepositoryFail(tmpDir, "origin", "master")) + + // Enable "Push To Create" + setting.Repository.EnablePushCreateUser = true + + // Assert that cloning from a non-existent repository does not create it and that it definitely wasn't create above + t.Run("FailToCloneFromNonExistentRepository", doGitCloneFail(u)) + + // Then "Push To Create"x + t.Run("SuccessfullyPushAndCreateTestRepository", doGitPushTestRepository(tmpDir, "origin", "master")) + + // Finally, fetch repo from database and ensure the correct repository has been created + repo, err := repo_model.GetRepositoryByOwnerAndName(ctx.Username, ctx.Reponame) + assert.NoError(t, err) + assert.False(t, repo.IsEmpty) + assert.True(t, repo.IsPrivate) + + // Now add a remote that is invalid to "Push To Create" + invalidCtx := ctx + invalidCtx.Reponame = fmt.Sprintf("invalid/repo-tmp-push-create-%s", u.Scheme) + u.Path = invalidCtx.GitPath() + t.Run("AddInvalidRemote", doGitAddRemote(tmpDir, "invalid", u)) + + // Fail to "Push To Create" the invalid + t.Run("FailToPushAndCreateInvalidTestRepository", doGitPushTestRepositoryFail(tmpDir, "invalid", "master")) + } +} + +func doBranchDelete(ctx APITestContext, owner, repo, branch string) func(*testing.T) { + return func(t *testing.T) { + csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/branches", url.PathEscape(owner), url.PathEscape(repo))) + + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/branches/delete?name=%s", url.PathEscape(owner), url.PathEscape(repo), url.QueryEscape(branch)), map[string]string{ + "_csrf": csrf, + }) + ctx.Session.MakeRequest(t, req, http.StatusOK) + } +} + +func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { + return func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame) + + t.Run("CheckoutProtected", doGitCheckoutBranch(dstPath, "protected")) + t.Run("PullProtected", doGitPull(dstPath, "origin", "protected")) + t.Run("GenerateCommit", func(t *testing.T) { + _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-") + assert.NoError(t, err) + }) + t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected3")) + var pr api.PullRequest + var err error + t.Run("CreatePullRequest", func(t *testing.T) { + pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "protected", "unprotected3")(t) + assert.NoError(t, err) + }) + + // Request repository commits page + req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/commits", baseCtx.Username, baseCtx.Reponame, pr.Index)) + resp := ctx.Session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + + // Get first commit URL + commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href") + assert.True(t, exists) + assert.NotEmpty(t, commitURL) + + commitID := path.Base(commitURL) + + // Call API to add Pending status for commit + t.Run("CreateStatus", doAPICreateCommitStatus(ctx, commitID, api.CommitStatusPending)) + + // Cancel not existing auto merge + ctx.ExpectedCode = http.StatusNotFound + t.Run("CancelAutoMergePR", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) + + // Add auto merge request + ctx.ExpectedCode = http.StatusCreated + t.Run("AutoMergePR", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) + + // Can not create schedule twice + ctx.ExpectedCode = http.StatusConflict + t.Run("AutoMergePRTwice", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) + + // Cancel auto merge request + ctx.ExpectedCode = http.StatusNoContent + t.Run("CancelAutoMergePR", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) + + // Add auto merge request + ctx.ExpectedCode = http.StatusCreated + t.Run("AutoMergePR", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) + + // Check pr status + ctx.ExpectedCode = 0 + pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) + assert.NoError(t, err) + assert.False(t, pr.HasMerged) + + // Call API to add Failure status for commit + t.Run("CreateStatus", doAPICreateCommitStatus(ctx, commitID, api.CommitStatusFailure)) + + // Check pr status + pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) + assert.NoError(t, err) + assert.False(t, pr.HasMerged) + + // Call API to add Success status for commit + t.Run("CreateStatus", doAPICreateCommitStatus(ctx, commitID, api.CommitStatusSuccess)) + + // wait to let gitea merge stuff + time.Sleep(time.Second) + + // test pr status + pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) + assert.NoError(t, err) + assert.True(t, pr.HasMerged) + } +} + +func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headBranch string) func(t *testing.T) { + return func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // skip this test if git version is low + if git.CheckGitVersionAtLeast("2.29") != nil { + return + } + + gitRepo, err := git.OpenRepository(git.DefaultContext, dstPath) + if !assert.NoError(t, err) { + return + } + defer gitRepo.Close() + + var ( + pr1, pr2 *issues_model.PullRequest + commit string + ) + repo, err := repo_model.GetRepositoryByOwnerAndName(ctx.Username, ctx.Reponame) + if !assert.NoError(t, err) { + return + } + + pullNum := unittest.GetCount(t, &issues_model.PullRequest{}) + + t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch)) + + t.Run("AddCommit", func(t *testing.T) { + err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0o666) + if !assert.NoError(t, err) { + return + } + + err = git.AddChanges(dstPath, true) + assert.NoError(t, err) + + err = git.CommitChanges(dstPath, git.CommitChangesOptions{ + Committer: &git.Signature{ + Email: "user2@example.com", + Name: "user2", + When: time.Now(), + }, + Author: &git.Signature{ + Email: "user2@example.com", + Name: "user2", + When: time.Now(), + }, + Message: "Testing commit 1", + }) + assert.NoError(t, err) + commit, err = gitRepo.GetRefCommitID("HEAD") + assert.NoError(t, err) + }) + + t.Run("Push", func(t *testing.T) { + err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o", "topic="+headBranch).Run(&git.RunOpts{Dir: dstPath}) + if !assert.NoError(t, err) { + return + } + unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+1) + pr1 = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ + HeadRepoID: repo.ID, + Flow: issues_model.PullRequestFlowAGit, + }) + if !assert.NotEmpty(t, pr1) { + return + } + prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, "user2/"+headBranch, pr1.HeadBranch) + assert.Equal(t, false, prMsg.HasMerged) + assert.Contains(t, "Testing commit 1", prMsg.Body) + assert.Equal(t, commit, prMsg.Head.Sha) + + _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master/test/"+headBranch).RunStdString(&git.RunOpts{Dir: dstPath}) + if !assert.NoError(t, err) { + return + } + unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) + pr2 = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ + HeadRepoID: repo.ID, + Index: pr1.Index + 1, + Flow: issues_model.PullRequestFlowAGit, + }) + if !assert.NotEmpty(t, pr2) { + return + } + prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, "user2/test/"+headBranch, pr2.HeadBranch) + assert.Equal(t, false, prMsg.HasMerged) + }) + + if pr1 == nil || pr2 == nil { + return + } + + t.Run("AddCommit2", func(t *testing.T) { + err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content \n ## test content 2"), 0o666) + if !assert.NoError(t, err) { + return + } + + err = git.AddChanges(dstPath, true) + assert.NoError(t, err) + + err = git.CommitChanges(dstPath, git.CommitChangesOptions{ + Committer: &git.Signature{ + Email: "user2@example.com", + Name: "user2", + When: time.Now(), + }, + Author: &git.Signature{ + Email: "user2@example.com", + Name: "user2", + When: time.Now(), + }, + Message: "Testing commit 2", + }) + assert.NoError(t, err) + commit, err = gitRepo.GetRefCommitID("HEAD") + assert.NoError(t, err) + }) + + t.Run("Push2", func(t *testing.T) { + err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o", "topic="+headBranch).Run(&git.RunOpts{Dir: dstPath}) + if !assert.NoError(t, err) { + return + } + unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) + prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, false, prMsg.HasMerged) + assert.Equal(t, commit, prMsg.Head.Sha) + + _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master/test/"+headBranch).RunStdString(&git.RunOpts{Dir: dstPath}) + if !assert.NoError(t, err) { + return + } + unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) + prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, false, prMsg.HasMerged) + assert.Equal(t, commit, prMsg.Head.Sha) + }) + t.Run("Merge", doAPIMergePullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)) + t.Run("CheckoutMasterAgain", doGitCheckoutBranch(dstPath, "master")) + } +} diff --git a/tests/integration/goget_test.go b/tests/integration/goget_test.go new file mode 100644 index 0000000000..c969f4aff1 --- /dev/null +++ b/tests/integration/goget_test.go @@ -0,0 +1,36 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestGoGet(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/blah/glah/plah?go-get=1") + resp := MakeRequest(t, req, http.StatusOK) + + expected := fmt.Sprintf(`<!doctype html> +<html> + <head> + <meta name="go-import" content="%[1]s:%[2]s/blah/glah git %[3]sblah/glah.git"> + <meta name="go-source" content="%[1]s:%[2]s/blah/glah _ %[3]sblah/glah/src/branch/master{/dir} %[3]sblah/glah/src/branch/master{/dir}/{file}#L{line}"> + </head> + <body> + go get --insecure %[1]s:%[2]s/blah/glah + </body> +</html>`, setting.Domain, setting.HTTPPort, setting.AppURL) + + assert.Equal(t, expected, resp.Body.String()) +} diff --git a/tests/integration/gpg_git_test.go b/tests/integration/gpg_git_test.go new file mode 100644 index 0000000000..2e16d150c8 --- /dev/null +++ b/tests/integration/gpg_git_test.go @@ -0,0 +1,369 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "encoding/base64" + "fmt" + "net/url" + "os" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/process" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" + "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/armor" +) + +func TestGPGGit(t *testing.T) { + defer tests.PrepareTestEnv(t)() + username := "user2" + + // OK Set a new GPG home + tmpDir, err := os.MkdirTemp("", "temp-gpg") + assert.NoError(t, err) + defer util.RemoveAll(tmpDir) + + err = os.Chmod(tmpDir, 0o700) + assert.NoError(t, err) + + oldGNUPGHome := os.Getenv("GNUPGHOME") + err = os.Setenv("GNUPGHOME", tmpDir) + assert.NoError(t, err) + defer os.Setenv("GNUPGHOME", oldGNUPGHome) + + // Need to create a root key + rootKeyPair, err := importTestingKey(tmpDir, "gitea", "gitea@fake.local") + assert.NoError(t, err) + if err != nil { + assert.FailNow(t, "Unable to import rootKeyPair") + } + + rootKeyID := rootKeyPair.PrimaryKey.KeyIdShortString() + + oldKeyID := setting.Repository.Signing.SigningKey + oldName := setting.Repository.Signing.SigningName + oldEmail := setting.Repository.Signing.SigningEmail + defer func() { + setting.Repository.Signing.SigningKey = oldKeyID + setting.Repository.Signing.SigningName = oldName + setting.Repository.Signing.SigningEmail = oldEmail + }() + + setting.Repository.Signing.SigningKey = rootKeyID + setting.Repository.Signing.SigningName = "gitea" + setting.Repository.Signing.SigningEmail = "gitea@fake.local" + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: username}) + + setting.Repository.Signing.InitialCommit = []string{"never"} + setting.Repository.Signing.CRUDActions = []string{"never"} + + baseAPITestContext := NewAPITestContext(t, username, "repo1") + onGiteaRun(t, func(t *testing.T, u *url.URL) { + u.Path = baseAPITestContext.GitPath() + + t.Run("Unsigned-Initial", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testCtx := NewAPITestContext(t, username, "initial-unsigned") + t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) + t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + assert.NotNil(t, branch.Commit) + assert.NotNil(t, branch.Commit.Verification) + assert.False(t, branch.Commit.Verification.Verified) + assert.Empty(t, branch.Commit.Verification.Signature) + })) + t.Run("CreateCRUDFile-Never", crudActionCreateFile( + t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) { + assert.False(t, response.Verification.Verified) + })) + t.Run("CreateCRUDFile-Never", crudActionCreateFile( + t, testCtx, user, "never", "never2", "unsigned-never2.txt", func(t *testing.T, response api.FileResponse) { + assert.False(t, response.Verification.Verified) + })) + }) + }, false) + setting.Repository.Signing.CRUDActions = []string{"parentsigned"} + onGiteaRun(t, func(t *testing.T, u *url.URL) { + u.Path = baseAPITestContext.GitPath() + + t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testCtx := NewAPITestContext(t, username, "initial-unsigned") + t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile( + t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) { + assert.False(t, response.Verification.Verified) + })) + t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile( + t, testCtx, user, "parentsigned", "parentsigned2", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) { + assert.False(t, response.Verification.Verified) + })) + }) + }, false) + setting.Repository.Signing.CRUDActions = []string{"never"} + onGiteaRun(t, func(t *testing.T, u *url.URL) { + u.Path = baseAPITestContext.GitPath() + + t.Run("Unsigned-Initial-CRUD-Never", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testCtx := NewAPITestContext(t, username, "initial-unsigned") + t.Run("CreateCRUDFile-Never", crudActionCreateFile( + t, testCtx, user, "parentsigned", "parentsigned-never", "unsigned-never2.txt", func(t *testing.T, response api.FileResponse) { + assert.False(t, response.Verification.Verified) + })) + }) + }, false) + setting.Repository.Signing.CRUDActions = []string{"always"} + onGiteaRun(t, func(t *testing.T, u *url.URL) { + u.Path = baseAPITestContext.GitPath() + + t.Run("Unsigned-Initial-CRUD-Always", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testCtx := NewAPITestContext(t, username, "initial-unsigned") + t.Run("CreateCRUDFile-Always", crudActionCreateFile( + t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) { + assert.NotNil(t, response.Verification) + if response.Verification == nil { + assert.FailNow(t, "no verification provided with response! %v", response) + return + } + assert.True(t, response.Verification.Verified) + if !response.Verification.Verified { + t.FailNow() + return + } + assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) + })) + t.Run("CreateCRUDFile-ParentSigned-always", crudActionCreateFile( + t, testCtx, user, "parentsigned", "parentsigned-always", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) { + assert.NotNil(t, response.Verification) + if response.Verification == nil { + assert.FailNow(t, "no verification provided with response! %v", response) + return + } + assert.True(t, response.Verification.Verified) + if !response.Verification.Verified { + t.FailNow() + return + } + assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) + })) + }) + }, false) + setting.Repository.Signing.CRUDActions = []string{"parentsigned"} + onGiteaRun(t, func(t *testing.T, u *url.URL) { + u.Path = baseAPITestContext.GitPath() + + t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testCtx := NewAPITestContext(t, username, "initial-unsigned") + t.Run("CreateCRUDFile-Always-ParentSigned", crudActionCreateFile( + t, testCtx, user, "always", "always-parentsigned", "signed-always-parentsigned.txt", func(t *testing.T, response api.FileResponse) { + assert.NotNil(t, response.Verification) + if response.Verification == nil { + assert.FailNow(t, "no verification provided with response! %v", response) + return + } + assert.True(t, response.Verification.Verified) + if !response.Verification.Verified { + t.FailNow() + return + } + assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) + })) + }) + }, false) + setting.Repository.Signing.InitialCommit = []string{"always"} + onGiteaRun(t, func(t *testing.T, u *url.URL) { + u.Path = baseAPITestContext.GitPath() + + t.Run("AlwaysSign-Initial", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testCtx := NewAPITestContext(t, username, "initial-always") + t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) + t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + assert.NotNil(t, branch.Commit) + if branch.Commit == nil { + assert.FailNow(t, "no commit provided with branch! %v", branch) + return + } + assert.NotNil(t, branch.Commit.Verification) + if branch.Commit.Verification == nil { + assert.FailNow(t, "no verification provided with branch commit! %v", branch.Commit) + return + } + assert.True(t, branch.Commit.Verification.Verified) + if !branch.Commit.Verification.Verified { + t.FailNow() + return + } + assert.Equal(t, "gitea@fake.local", branch.Commit.Verification.Signer.Email) + })) + }) + }, false) + setting.Repository.Signing.CRUDActions = []string{"never"} + onGiteaRun(t, func(t *testing.T, u *url.URL) { + u.Path = baseAPITestContext.GitPath() + + t.Run("AlwaysSign-Initial-CRUD-Never", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testCtx := NewAPITestContext(t, username, "initial-always-never") + t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) + t.Run("CreateCRUDFile-Never", crudActionCreateFile( + t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) { + assert.False(t, response.Verification.Verified) + })) + }) + }, false) + setting.Repository.Signing.CRUDActions = []string{"parentsigned"} + onGiteaRun(t, func(t *testing.T, u *url.URL) { + u.Path = baseAPITestContext.GitPath() + t.Run("AlwaysSign-Initial-CRUD-ParentSigned-On-Always", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testCtx := NewAPITestContext(t, username, "initial-always-parent") + t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) + t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile( + t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) { + assert.True(t, response.Verification.Verified) + if !response.Verification.Verified { + t.FailNow() + return + } + assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) + })) + }) + }, false) + setting.Repository.Signing.CRUDActions = []string{"always"} + onGiteaRun(t, func(t *testing.T, u *url.URL) { + u.Path = baseAPITestContext.GitPath() + + t.Run("AlwaysSign-Initial-CRUD-Always", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testCtx := NewAPITestContext(t, username, "initial-always-always") + t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) + t.Run("CreateCRUDFile-Always", crudActionCreateFile( + t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) { + assert.True(t, response.Verification.Verified) + if !response.Verification.Verified { + t.FailNow() + return + } + assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) + })) + }) + }, false) + var pr api.PullRequest + setting.Repository.Signing.Merges = []string{"commitssigned"} + onGiteaRun(t, func(t *testing.T, u *url.URL) { + u.Path = baseAPITestContext.GitPath() + + t.Run("UnsignedMerging", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testCtx := NewAPITestContext(t, username, "initial-unsigned") + var err error + t.Run("CreatePullRequest", func(t *testing.T) { + pr, err = doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "never2")(t) + assert.NoError(t, err) + }) + t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index)) + t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + assert.NotNil(t, branch.Commit) + assert.NotNil(t, branch.Commit.Verification) + assert.False(t, branch.Commit.Verification.Verified) + assert.Empty(t, branch.Commit.Verification.Signature) + })) + }) + }, false) + setting.Repository.Signing.Merges = []string{"basesigned"} + onGiteaRun(t, func(t *testing.T, u *url.URL) { + u.Path = baseAPITestContext.GitPath() + + t.Run("BaseSignedMerging", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testCtx := NewAPITestContext(t, username, "initial-unsigned") + var err error + t.Run("CreatePullRequest", func(t *testing.T) { + pr, err = doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "parentsigned2")(t) + assert.NoError(t, err) + }) + t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index)) + t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + assert.NotNil(t, branch.Commit) + assert.NotNil(t, branch.Commit.Verification) + assert.False(t, branch.Commit.Verification.Verified) + assert.Empty(t, branch.Commit.Verification.Signature) + })) + }) + }, false) + setting.Repository.Signing.Merges = []string{"commitssigned"} + onGiteaRun(t, func(t *testing.T, u *url.URL) { + u.Path = baseAPITestContext.GitPath() + + t.Run("CommitsSignedMerging", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testCtx := NewAPITestContext(t, username, "initial-unsigned") + var err error + t.Run("CreatePullRequest", func(t *testing.T) { + pr, err = doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "always-parentsigned")(t) + assert.NoError(t, err) + }) + t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index)) + t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + assert.NotNil(t, branch.Commit) + assert.NotNil(t, branch.Commit.Verification) + assert.True(t, branch.Commit.Verification.Verified) + })) + }) + }, false) +} + +func crudActionCreateFile(t *testing.T, ctx APITestContext, user *user_model.User, from, to, path string, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) { + return doAPICreateFile(ctx, path, &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + BranchName: from, + NewBranchName: to, + Message: fmt.Sprintf("from:%s to:%s path:%s", from, to, path), + Author: api.Identity{ + Name: user.FullName, + Email: user.Email, + }, + Committer: api.Identity{ + Name: user.FullName, + Email: user.Email, + }, + }, + Content: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("This is new text for %s", path))), + }, callback...) +} + +func importTestingKey(tmpDir, name, email string) (*openpgp.Entity, error) { + if _, _, err := process.GetManager().Exec("gpg --import tests/integration/private-testing.key", "gpg", "--import", "tests/integration/private-testing.key"); err != nil { + return nil, err + } + keyringFile, err := os.Open("tests/integration/private-testing.key") + if err != nil { + return nil, err + } + defer keyringFile.Close() + + block, err := armor.Decode(keyringFile) + if err != nil { + return nil, err + } + + keyring, err := openpgp.ReadKeyRing(block.Body) + if err != nil { + return nil, fmt.Errorf("Keyring access failed: '%v'", err) + } + + // There should only be one entity in this file. + return keyring[0], nil +} diff --git a/tests/integration/html_helper.go b/tests/integration/html_helper.go new file mode 100644 index 0000000000..35d61f7b3e --- /dev/null +++ b/tests/integration/html_helper.go @@ -0,0 +1,60 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "bytes" + "testing" + + "github.com/PuerkitoBio/goquery" + "github.com/stretchr/testify/assert" +) + +// HTMLDoc struct +type HTMLDoc struct { + doc *goquery.Document +} + +// NewHTMLParser parse html file +func NewHTMLParser(t testing.TB, body *bytes.Buffer) *HTMLDoc { + t.Helper() + doc, err := goquery.NewDocumentFromReader(body) + assert.NoError(t, err) + return &HTMLDoc{doc: doc} +} + +// GetInputValueByID for get input value by id +func (doc *HTMLDoc) GetInputValueByID(id string) string { + text, _ := doc.doc.Find("#" + id).Attr("value") + return text +} + +// GetInputValueByName for get input value by name +func (doc *HTMLDoc) GetInputValueByName(name string) string { + text, _ := doc.doc.Find("input[name=\"" + name + "\"]").Attr("value") + return text +} + +// Find gets the descendants of each element in the current set of +// matched elements, filtered by a selector. It returns a new Selection +// object containing these matched elements. +func (doc *HTMLDoc) Find(selector string) *goquery.Selection { + return doc.doc.Find(selector) +} + +// GetCSRF for getting CSRF token value from input +func (doc *HTMLDoc) GetCSRF() string { + return doc.GetInputValueByName("_csrf") +} + +// AssertElement check if element by selector exists or does not exist depending on checkExists +func (doc *HTMLDoc) AssertElement(t testing.TB, selector string, checkExists bool) { + sel := doc.doc.Find(selector) + if checkExists { + assert.Equal(t, 1, sel.Length()) + } else { + assert.Equal(t, 0, sel.Length()) + } +} diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go new file mode 100644 index 0000000000..8fc8a854a7 --- /dev/null +++ b/tests/integration/integration_test.go @@ -0,0 +1,407 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "bytes" + "context" + "fmt" + "hash" + "hash/fnv" + "io" + "net/http" + "net/http/cookiejar" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers" + "code.gitea.io/gitea/tests" + + "github.com/PuerkitoBio/goquery" + "github.com/stretchr/testify/assert" +) + +var c *web.Route + +type NilResponseRecorder struct { + httptest.ResponseRecorder + Length int +} + +func (n *NilResponseRecorder) Write(b []byte) (int, error) { + n.Length += len(b) + return len(b), nil +} + +// NewRecorder returns an initialized ResponseRecorder. +func NewNilResponseRecorder() *NilResponseRecorder { + return &NilResponseRecorder{ + ResponseRecorder: *httptest.NewRecorder(), + } +} + +type NilResponseHashSumRecorder struct { + httptest.ResponseRecorder + Hash hash.Hash + Length int +} + +func (n *NilResponseHashSumRecorder) Write(b []byte) (int, error) { + _, _ = n.Hash.Write(b) + n.Length += len(b) + return len(b), nil +} + +// NewRecorder returns an initialized ResponseRecorder. +func NewNilResponseHashSumRecorder() *NilResponseHashSumRecorder { + return &NilResponseHashSumRecorder{ + Hash: fnv.New32(), + ResponseRecorder: *httptest.NewRecorder(), + } +} + +func TestMain(m *testing.M) { + defer log.Close() + + managerCtx, cancel := context.WithCancel(context.Background()) + graceful.InitManager(managerCtx) + defer cancel() + + tests.InitTest(true) + c = routers.NormalRoutes(context.TODO()) + + // integration test settings... + if setting.Cfg != nil { + testingCfg := setting.Cfg.Section("integration-tests") + tests.SlowTest = testingCfg.Key("SLOW_TEST").MustDuration(tests.SlowTest) + tests.SlowFlush = testingCfg.Key("SLOW_FLUSH").MustDuration(tests.SlowFlush) + } + + if os.Getenv("GITEA_SLOW_TEST_TIME") != "" { + duration, err := time.ParseDuration(os.Getenv("GITEA_SLOW_TEST_TIME")) + if err == nil { + tests.SlowTest = duration + } + } + + if os.Getenv("GITEA_SLOW_FLUSH_TIME") != "" { + duration, err := time.ParseDuration(os.Getenv("GITEA_SLOW_FLUSH_TIME")) + if err == nil { + tests.SlowFlush = duration + } + } + + os.Unsetenv("GIT_AUTHOR_NAME") + os.Unsetenv("GIT_AUTHOR_EMAIL") + os.Unsetenv("GIT_AUTHOR_DATE") + os.Unsetenv("GIT_COMMITTER_NAME") + os.Unsetenv("GIT_COMMITTER_EMAIL") + os.Unsetenv("GIT_COMMITTER_DATE") + + err := unittest.InitFixtures( + unittest.FixturesOptions{ + Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"), + }, + ) + if err != nil { + fmt.Printf("Error initializing test database: %v\n", err) + os.Exit(1) + } + exitCode := m.Run() + + tests.WriterCloser.Reset() + + if err = util.RemoveAll(setting.Indexer.IssuePath); err != nil { + fmt.Printf("util.RemoveAll: %v\n", err) + os.Exit(1) + } + if err = util.RemoveAll(setting.Indexer.RepoPath); err != nil { + fmt.Printf("Unable to remove repo indexer: %v\n", err) + os.Exit(1) + } + + os.Exit(exitCode) +} + +type TestSession struct { + jar http.CookieJar +} + +func (s *TestSession) GetCookie(name string) *http.Cookie { + baseURL, err := url.Parse(setting.AppURL) + if err != nil { + return nil + } + + for _, c := range s.jar.Cookies(baseURL) { + if c.Name == name { + return c + } + } + return nil +} + +func (s *TestSession) MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder { + t.Helper() + baseURL, err := url.Parse(setting.AppURL) + assert.NoError(t, err) + for _, c := range s.jar.Cookies(baseURL) { + req.AddCookie(c) + } + resp := MakeRequest(t, req, expectedStatus) + + ch := http.Header{} + ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";")) + cr := http.Request{Header: ch} + s.jar.SetCookies(baseURL, cr.Cookies()) + + return resp +} + +func (s *TestSession) MakeRequestNilResponseRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseRecorder { + t.Helper() + baseURL, err := url.Parse(setting.AppURL) + assert.NoError(t, err) + for _, c := range s.jar.Cookies(baseURL) { + req.AddCookie(c) + } + resp := MakeRequestNilResponseRecorder(t, req, expectedStatus) + + ch := http.Header{} + ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";")) + cr := http.Request{Header: ch} + s.jar.SetCookies(baseURL, cr.Cookies()) + + return resp +} + +func (s *TestSession) MakeRequestNilResponseHashSumRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseHashSumRecorder { + t.Helper() + baseURL, err := url.Parse(setting.AppURL) + assert.NoError(t, err) + for _, c := range s.jar.Cookies(baseURL) { + req.AddCookie(c) + } + resp := MakeRequestNilResponseHashSumRecorder(t, req, expectedStatus) + + ch := http.Header{} + ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";")) + cr := http.Request{Header: ch} + s.jar.SetCookies(baseURL, cr.Cookies()) + + return resp +} + +const userPassword = "password" + +var loginSessionCache = make(map[string]*TestSession, 10) + +func emptyTestSession(t testing.TB) *TestSession { + t.Helper() + jar, err := cookiejar.New(nil) + assert.NoError(t, err) + + return &TestSession{jar: jar} +} + +func getUserToken(t testing.TB, userName string) string { + return getTokenForLoggedInUser(t, loginUser(t, userName)) +} + +func loginUser(t testing.TB, userName string) *TestSession { + t.Helper() + if session, ok := loginSessionCache[userName]; ok { + return session + } + session := loginUserWithPassword(t, userName, userPassword) + loginSessionCache[userName] = session + return session +} + +func loginUserWithPassword(t testing.TB, userName, password string) *TestSession { + t.Helper() + req := NewRequest(t, "GET", "/user/login") + resp := MakeRequest(t, req, http.StatusOK) + + doc := NewHTMLParser(t, resp.Body) + req = NewRequestWithValues(t, "POST", "/user/login", map[string]string{ + "_csrf": doc.GetCSRF(), + "user_name": userName, + "password": password, + }) + resp = MakeRequest(t, req, http.StatusSeeOther) + + ch := http.Header{} + ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";")) + cr := http.Request{Header: ch} + + session := emptyTestSession(t) + + baseURL, err := url.Parse(setting.AppURL) + assert.NoError(t, err) + session.jar.SetCookies(baseURL, cr.Cookies()) + + return session +} + +// token has to be unique this counter take care of +var tokenCounter int64 + +func getTokenForLoggedInUser(t testing.TB, session *TestSession) string { + t.Helper() + tokenCounter++ + req := NewRequest(t, "GET", "/user/settings/applications") + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + req = NewRequestWithValues(t, "POST", "/user/settings/applications", map[string]string{ + "_csrf": doc.GetCSRF(), + "name": fmt.Sprintf("api-testing-token-%d", tokenCounter), + }) + session.MakeRequest(t, req, http.StatusSeeOther) + req = NewRequest(t, "GET", "/user/settings/applications") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + token := htmlDoc.doc.Find(".ui.info p").Text() + return token +} + +func NewRequest(t testing.TB, method, urlStr string) *http.Request { + t.Helper() + return NewRequestWithBody(t, method, urlStr, nil) +} + +func NewRequestf(t testing.TB, method, urlFormat string, args ...interface{}) *http.Request { + t.Helper() + return NewRequest(t, method, fmt.Sprintf(urlFormat, args...)) +} + +func NewRequestWithValues(t testing.TB, method, urlStr string, values map[string]string) *http.Request { + t.Helper() + urlValues := url.Values{} + for key, value := range values { + urlValues[key] = []string{value} + } + req := NewRequestWithBody(t, method, urlStr, bytes.NewBufferString(urlValues.Encode())) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + return req +} + +func NewRequestWithJSON(t testing.TB, method, urlStr string, v interface{}) *http.Request { + t.Helper() + + jsonBytes, err := json.Marshal(v) + assert.NoError(t, err) + req := NewRequestWithBody(t, method, urlStr, bytes.NewBuffer(jsonBytes)) + req.Header.Add("Content-Type", "application/json") + return req +} + +func NewRequestWithBody(t testing.TB, method, urlStr string, body io.Reader) *http.Request { + t.Helper() + if !strings.HasPrefix(urlStr, "http") && !strings.HasPrefix(urlStr, "/") { + urlStr = "/" + urlStr + } + request, err := http.NewRequest(method, urlStr, body) + assert.NoError(t, err) + request.RequestURI = urlStr + return request +} + +func AddBasicAuthHeader(request *http.Request, username string) *http.Request { + request.SetBasicAuth(username, userPassword) + return request +} + +const NoExpectedStatus = -1 + +func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder { + t.Helper() + recorder := httptest.NewRecorder() + c.ServeHTTP(recorder, req) + if expectedStatus != NoExpectedStatus { + if !assert.EqualValues(t, expectedStatus, recorder.Code, + "Request: %s %s", req.Method, req.URL.String()) { + logUnexpectedResponse(t, recorder) + } + } + return recorder +} + +func MakeRequestNilResponseRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseRecorder { + t.Helper() + recorder := NewNilResponseRecorder() + c.ServeHTTP(recorder, req) + if expectedStatus != NoExpectedStatus { + if !assert.EqualValues(t, expectedStatus, recorder.Code, + "Request: %s %s", req.Method, req.URL.String()) { + logUnexpectedResponse(t, &recorder.ResponseRecorder) + } + } + return recorder +} + +func MakeRequestNilResponseHashSumRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseHashSumRecorder { + t.Helper() + recorder := NewNilResponseHashSumRecorder() + c.ServeHTTP(recorder, req) + if expectedStatus != NoExpectedStatus { + if !assert.EqualValues(t, expectedStatus, recorder.Code, + "Request: %s %s", req.Method, req.URL.String()) { + logUnexpectedResponse(t, &recorder.ResponseRecorder) + } + } + return recorder +} + +// logUnexpectedResponse logs the contents of an unexpected response. +func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) { + t.Helper() + respBytes := recorder.Body.Bytes() + if len(respBytes) == 0 { + return + } else if len(respBytes) < 500 { + // if body is short, just log the whole thing + t.Log("Response:", string(respBytes)) + return + } + + // log the "flash" error message, if one exists + // we must create a new buffer, so that we don't "use up" resp.Body + htmlDoc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(respBytes)) + if err != nil { + return // probably a non-HTML response + } + errMsg := htmlDoc.Find(".ui.negative.message").Text() + if len(errMsg) > 0 { + t.Log("A flash error message was found:", errMsg) + } +} + +func DecodeJSON(t testing.TB, resp *httptest.ResponseRecorder, v interface{}) { + t.Helper() + + decoder := json.NewDecoder(resp.Body) + assert.NoError(t, decoder.Decode(v)) +} + +func GetCSRF(t testing.TB, session *TestSession, urlStr string) string { + t.Helper() + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + return doc.GetCSRF() +} diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go new file mode 100644 index 0000000000..1f0f894ca4 --- /dev/null +++ b/tests/integration/issue_test.go @@ -0,0 +1,559 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "path" + "strconv" + "strings" + "testing" + "time" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/indexer/issues" + "code.gitea.io/gitea/modules/references" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/tests" + + "github.com/PuerkitoBio/goquery" + "github.com/stretchr/testify/assert" +) + +func getIssuesSelection(t testing.TB, htmlDoc *HTMLDoc) *goquery.Selection { + issueList := htmlDoc.doc.Find(".issue.list") + assert.EqualValues(t, 1, issueList.Length()) + return issueList.Find("li").Find(".title") +} + +func getIssue(t *testing.T, repoID int64, issueSelection *goquery.Selection) *issues_model.Issue { + href, exists := issueSelection.Attr("href") + assert.True(t, exists) + indexStr := href[strings.LastIndexByte(href, '/')+1:] + index, err := strconv.Atoi(indexStr) + assert.NoError(t, err, "Invalid issue href: %s", href) + return unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repoID, Index: int64(index)}) +} + +func assertMatch(t testing.TB, issue *issues_model.Issue, keyword string) { + matches := strings.Contains(strings.ToLower(issue.Title), keyword) || + strings.Contains(strings.ToLower(issue.Content), keyword) + for _, comment := range issue.Comments { + matches = matches || strings.Contains( + strings.ToLower(comment.Content), + keyword, + ) + } + assert.True(t, matches) +} + +func TestNoLoginViewIssues(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/user2/repo1/issues") + MakeRequest(t, req, http.StatusOK) +} + +func TestViewIssuesSortByType(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + session := loginUser(t, user.Name) + req := NewRequest(t, "GET", repo.Link()+"/issues?type=created_by") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + issuesSelection := getIssuesSelection(t, htmlDoc) + expectedNumIssues := unittest.GetCount(t, + &issues_model.Issue{RepoID: repo.ID, PosterID: user.ID}, + unittest.Cond("is_closed=?", false), + unittest.Cond("is_pull=?", false), + ) + if expectedNumIssues > setting.UI.IssuePagingNum { + expectedNumIssues = setting.UI.IssuePagingNum + } + assert.EqualValues(t, expectedNumIssues, issuesSelection.Length()) + + issuesSelection.Each(func(_ int, selection *goquery.Selection) { + issue := getIssue(t, repo.ID, selection) + assert.EqualValues(t, user.ID, issue.PosterID) + }) +} + +func TestViewIssuesKeyword(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ + RepoID: repo.ID, + Index: 1, + }) + issues.UpdateIssueIndexer(issue) + time.Sleep(time.Second * 1) + const keyword = "first" + req := NewRequestf(t, "GET", "%s/issues?q=%s", repo.Link(), keyword) + resp := MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + issuesSelection := getIssuesSelection(t, htmlDoc) + assert.EqualValues(t, 1, issuesSelection.Length()) + issuesSelection.Each(func(_ int, selection *goquery.Selection) { + issue := getIssue(t, repo.ID, selection) + assert.False(t, issue.IsClosed) + assert.False(t, issue.IsPull) + assertMatch(t, issue, keyword) + }) +} + +func TestNoLoginViewIssue(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/user2/repo1/issues/1") + MakeRequest(t, req, http.StatusOK) +} + +func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content string) string { + req := NewRequest(t, "GET", path.Join(user, repo, "issues", "new")) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + link, exists := htmlDoc.doc.Find("form.ui.form").Attr("action") + assert.True(t, exists, "The template has changed") + req = NewRequestWithValues(t, "POST", link, map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "title": title, + "content": content, + }) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + + issueURL := test.RedirectURL(resp) + req = NewRequest(t, "GET", issueURL) + resp = session.MakeRequest(t, req, http.StatusOK) + + htmlDoc = NewHTMLParser(t, resp.Body) + val := htmlDoc.doc.Find("#issue-title").Text() + assert.Equal(t, title, val) + val = htmlDoc.doc.Find(".comment .render-content p").First().Text() + assert.Equal(t, content, val) + + return issueURL +} + +func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, status string) int64 { + req := NewRequest(t, "GET", issueURL) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + link, exists := htmlDoc.doc.Find("#comment-form").Attr("action") + assert.True(t, exists, "The template has changed") + + commentCount := htmlDoc.doc.Find(".comment-list .comment .render-content").Length() + + req = NewRequestWithValues(t, "POST", link, map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "content": content, + "status": status, + }) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + + req = NewRequest(t, "GET", test.RedirectURL(resp)) + resp = session.MakeRequest(t, req, http.StatusOK) + + htmlDoc = NewHTMLParser(t, resp.Body) + + val := htmlDoc.doc.Find(".comment-list .comment .render-content p").Eq(commentCount).Text() + assert.Equal(t, content, val) + + idAttr, has := htmlDoc.doc.Find(".comment-list .comment").Eq(commentCount).Attr("id") + idStr := idAttr[strings.LastIndexByte(idAttr, '-')+1:] + assert.True(t, has) + id, err := strconv.Atoi(idStr) + assert.NoError(t, err) + return int64(id) +} + +func TestNewIssue(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + testNewIssue(t, session, "user2", "repo1", "Title", "Description") +} + +func TestIssueCommentClose(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description") + testIssueAddComment(t, session, issueURL, "Test comment 1", "") + testIssueAddComment(t, session, issueURL, "Test comment 2", "") + testIssueAddComment(t, session, issueURL, "Test comment 3", "close") + + // Validate that issue content has not been updated + req := NewRequest(t, "GET", issueURL) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + val := htmlDoc.doc.Find(".comment-list .comment .render-content p").First().Text() + assert.Equal(t, "Description", val) +} + +func TestIssueReaction(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description") + + req := NewRequest(t, "GET", issueURL) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + req = NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/react"), map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "content": "8ball", + }) + session.MakeRequest(t, req, http.StatusInternalServerError) + req = NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/react"), map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "content": "eyes", + }) + session.MakeRequest(t, req, http.StatusOK) + req = NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/unreact"), map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "content": "eyes", + }) + session.MakeRequest(t, req, http.StatusOK) +} + +func TestIssueCrossReference(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // Issue that will be referenced + _, issueBase := testIssueWithBean(t, "user2", 1, "Title", "Description") + + // Ref from issue title + issueRefURL, issueRef := testIssueWithBean(t, "user2", 1, fmt.Sprintf("Title ref #%d", issueBase.Index), "Description") + unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, + RefCommentID: 0, + RefIsPull: false, + RefAction: references.XRefActionNone, + }) + + // Edit title, neuter ref + testIssueChangeInfo(t, "user2", issueRefURL, "title", "Title no ref") + unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, + RefCommentID: 0, + RefIsPull: false, + RefAction: references.XRefActionNeutered, + }) + + // Ref from issue content + issueRefURL, issueRef = testIssueWithBean(t, "user2", 1, "TitleXRef", fmt.Sprintf("Description ref #%d", issueBase.Index)) + unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, + RefCommentID: 0, + RefIsPull: false, + RefAction: references.XRefActionNone, + }) + + // Edit content, neuter ref + testIssueChangeInfo(t, "user2", issueRefURL, "content", "Description no ref") + unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, + RefCommentID: 0, + RefIsPull: false, + RefAction: references.XRefActionNeutered, + }) + + // Ref from a comment + session := loginUser(t, "user2") + commentID := testIssueAddComment(t, session, issueRefURL, fmt.Sprintf("Adding ref from comment #%d", issueBase.Index), "") + comment := &issues_model.Comment{ + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, + RefCommentID: commentID, + RefIsPull: false, + RefAction: references.XRefActionNone, + } + unittest.AssertExistsAndLoadBean(t, comment) + + // Ref from a different repository + _, issueRef = testIssueWithBean(t, "user12", 10, "TitleXRef", fmt.Sprintf("Description ref user2/repo1#%d", issueBase.Index)) + unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ + IssueID: issueBase.ID, + RefRepoID: 10, + RefIssueID: issueRef.ID, + RefCommentID: 0, + RefIsPull: false, + RefAction: references.XRefActionNone, + }) +} + +func testIssueWithBean(t *testing.T, user string, repoID int64, title, content string) (string, *issues_model.Issue) { + session := loginUser(t, user) + issueURL := testNewIssue(t, session, user, fmt.Sprintf("repo%d", repoID), title, content) + indexStr := issueURL[strings.LastIndexByte(issueURL, '/')+1:] + index, err := strconv.Atoi(indexStr) + assert.NoError(t, err, "Invalid issue href: %s", issueURL) + issue := &issues_model.Issue{RepoID: repoID, Index: int64(index)} + unittest.AssertExistsAndLoadBean(t, issue) + return issueURL, issue +} + +func testIssueChangeInfo(t *testing.T, user, issueURL, info, value string) { + session := loginUser(t, user) + + req := NewRequest(t, "GET", issueURL) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + req = NewRequestWithValues(t, "POST", path.Join(issueURL, info), map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + info: value, + }) + _ = session.MakeRequest(t, req, http.StatusOK) +} + +func TestIssueRedirect(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + + // Test external tracker where style not set (shall default numeric) + req := NewRequest(t, "GET", path.Join("org26", "repo_external_tracker", "issues", "1")) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "https://tracker.com/org26/repo_external_tracker/issues/1", test.RedirectURL(resp)) + + // Test external tracker with numeric style + req = NewRequest(t, "GET", path.Join("org26", "repo_external_tracker_numeric", "issues", "1")) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "https://tracker.com/org26/repo_external_tracker_numeric/issues/1", test.RedirectURL(resp)) + + // Test external tracker with alphanumeric style (for a pull request) + req = NewRequest(t, "GET", path.Join("org26", "repo_external_tracker_alpha", "issues", "1")) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/"+path.Join("org26", "repo_external_tracker_alpha", "pulls", "1"), test.RedirectURL(resp)) +} + +func TestSearchIssues(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + + expectedIssueCount := 15 // from the fixtures + if expectedIssueCount > setting.UI.IssuePagingNum { + expectedIssueCount = setting.UI.IssuePagingNum + } + + link, _ := url.Parse("/issues/search") + req := NewRequest(t, "GET", link.String()) + resp := session.MakeRequest(t, req, http.StatusOK) + var apiIssues []*api.Issue + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, expectedIssueCount) + + since := "2000-01-01T00%3A50%3A01%2B00%3A00" // 946687801 + before := time.Unix(999307200, 0).Format(time.RFC3339) + query := url.Values{} + query.Add("since", since) + query.Add("before", before) + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 8) + query.Del("since") + query.Del("before") + + query.Add("state", "closed") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + query.Set("state", "all") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.EqualValues(t, "17", resp.Header().Get("X-Total-Count")) + assert.Len(t, apiIssues, 17) + + query.Add("limit", "5") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.EqualValues(t, "17", resp.Header().Get("X-Total-Count")) + assert.Len(t, apiIssues, 5) + + query = url.Values{"assigned": {"true"}, "state": {"all"}} + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + query = url.Values{"milestones": {"milestone1"}, "state": {"all"}} + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 1) + + query = url.Values{"milestones": {"milestone1,milestone3"}, "state": {"all"}} + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + query = url.Values{"owner": {"user2"}} // user + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 6) + + query = url.Values{"owner": {"user3"}} // organization + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 5) + + query = url.Values{"owner": {"user3"}, "team": {"team1"}} // organization + team + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) +} + +func TestSearchIssuesWithLabels(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + expectedIssueCount := 15 // from the fixtures + if expectedIssueCount > setting.UI.IssuePagingNum { + expectedIssueCount = setting.UI.IssuePagingNum + } + + session := loginUser(t, "user1") + link, _ := url.Parse("/issues/search") + query := url.Values{} + var apiIssues []*api.Issue + + link.RawQuery = query.Encode() + req := NewRequest(t, "GET", link.String()) + resp := session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, expectedIssueCount) + + query.Add("labels", "label1") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + // multiple labels + query.Set("labels", "label1,label2") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + // an org label + query.Set("labels", "orglabel4") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 1) + + // org and repo label + query.Set("labels", "label2,orglabel4") + query.Add("state", "all") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + // org and repo label which share the same issue + query.Set("labels", "label1,orglabel4") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) +} + +func TestGetIssueInfo(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + assert.NoError(t, issue.LoadAttributes(db.DefaultContext)) + assert.Equal(t, int64(1019307200), int64(issue.DeadlineUnix)) + assert.Equal(t, api.StateOpen, issue.State()) + + session := loginUser(t, owner.Name) + + urlStr := fmt.Sprintf("/%s/%s/issues/%d/info", owner.Name, repo.Name, issue.Index) + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + var apiIssue api.Issue + DecodeJSON(t, resp, &apiIssue) + + assert.EqualValues(t, issue.ID, apiIssue.ID) +} + +func TestUpdateIssueDeadline(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) + repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID}) + assert.NoError(t, issueBefore.LoadAttributes(db.DefaultContext)) + assert.Equal(t, int64(1019307200), int64(issueBefore.DeadlineUnix)) + assert.Equal(t, api.StateOpen, issueBefore.State()) + + session := loginUser(t, owner.Name) + + issueURL := fmt.Sprintf("%s/%s/issues/%d", owner.Name, repoBefore.Name, issueBefore.Index) + req := NewRequest(t, "GET", issueURL) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + urlStr := issueURL + "/deadline?_csrf=" + htmlDoc.GetCSRF() + req = NewRequestWithJSON(t, "POST", urlStr, map[string]string{ + "due_date": "2022-04-06T00:00:00.000Z", + }) + + resp = session.MakeRequest(t, req, http.StatusCreated) + var apiIssue api.IssueDeadline + DecodeJSON(t, resp, &apiIssue) + + assert.EqualValues(t, "2022-04-06", apiIssue.Deadline.Format("2006-01-02")) +} diff --git a/tests/integration/lfs_getobject_test.go b/tests/integration/lfs_getobject_test.go new file mode 100644 index 0000000000..f2b0ac80c3 --- /dev/null +++ b/tests/integration/lfs_getobject_test.go @@ -0,0 +1,186 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "archive/zip" + "bytes" + "io" + "net/http" + "net/http/httptest" + "testing" + + git_model "code.gitea.io/gitea/models/git" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/lfs" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/routers/web" + "code.gitea.io/gitea/tests" + + gzipp "github.com/klauspost/compress/gzip" + "github.com/stretchr/testify/assert" +) + +func storeObjectInRepo(t *testing.T, repositoryID int64, content *[]byte) string { + pointer, err := lfs.GeneratePointer(bytes.NewReader(*content)) + assert.NoError(t, err) + + _, err = git_model.NewLFSMetaObject(&git_model.LFSMetaObject{Pointer: pointer, RepositoryID: repositoryID}) + assert.NoError(t, err) + contentStore := lfs.NewContentStore() + exist, err := contentStore.Exists(pointer) + assert.NoError(t, err) + if !exist { + err := contentStore.Put(pointer, bytes.NewReader(*content)) + assert.NoError(t, err) + } + return pointer.Oid +} + +func storeAndGetLfs(t *testing.T, content *[]byte, extraHeader *http.Header, expectedStatus int) *httptest.ResponseRecorder { + repo, err := repo_model.GetRepositoryByOwnerAndName("user2", "repo1") + assert.NoError(t, err) + oid := storeObjectInRepo(t, repo.ID, content) + defer git_model.RemoveLFSMetaObjectByOid(repo.ID, oid) + + session := loginUser(t, "user2") + + // Request OID + req := NewRequest(t, "GET", "/user2/repo1.git/info/lfs/objects/"+oid+"/test") + req.Header.Set("Accept-Encoding", "gzip") + if extraHeader != nil { + for key, values := range *extraHeader { + for _, value := range values { + req.Header.Add(key, value) + } + } + } + + resp := session.MakeRequest(t, req, expectedStatus) + + return resp +} + +func checkResponseTestContentEncoding(t *testing.T, content *[]byte, resp *httptest.ResponseRecorder, expectGzip bool) { + contentEncoding := resp.Header().Get("Content-Encoding") + if !expectGzip || !setting.EnableGzip { + assert.NotContains(t, contentEncoding, "gzip") + + result := resp.Body.Bytes() + assert.Equal(t, *content, result) + } else { + assert.Contains(t, contentEncoding, "gzip") + gzippReader, err := gzipp.NewReader(resp.Body) + assert.NoError(t, err) + result, err := io.ReadAll(gzippReader) + assert.NoError(t, err) + assert.Equal(t, *content, result) + } +} + +func TestGetLFSSmall(t *testing.T) { + defer tests.PrepareTestEnv(t)() + content := []byte("A very small file\n") + + resp := storeAndGetLfs(t, &content, nil, http.StatusOK) + checkResponseTestContentEncoding(t, &content, resp, false) +} + +func TestGetLFSLarge(t *testing.T) { + defer tests.PrepareTestEnv(t)() + content := make([]byte, web.GzipMinSize*10) + for i := range content { + content[i] = byte(i % 256) + } + + resp := storeAndGetLfs(t, &content, nil, http.StatusOK) + checkResponseTestContentEncoding(t, &content, resp, true) +} + +func TestGetLFSGzip(t *testing.T) { + defer tests.PrepareTestEnv(t)() + b := make([]byte, web.GzipMinSize*10) + for i := range b { + b[i] = byte(i % 256) + } + outputBuffer := bytes.NewBuffer([]byte{}) + gzippWriter := gzipp.NewWriter(outputBuffer) + gzippWriter.Write(b) + gzippWriter.Close() + content := outputBuffer.Bytes() + + resp := storeAndGetLfs(t, &content, nil, http.StatusOK) + checkResponseTestContentEncoding(t, &content, resp, false) +} + +func TestGetLFSZip(t *testing.T) { + defer tests.PrepareTestEnv(t)() + b := make([]byte, web.GzipMinSize*10) + for i := range b { + b[i] = byte(i % 256) + } + outputBuffer := bytes.NewBuffer([]byte{}) + zipWriter := zip.NewWriter(outputBuffer) + fileWriter, err := zipWriter.Create("default") + assert.NoError(t, err) + fileWriter.Write(b) + zipWriter.Close() + content := outputBuffer.Bytes() + + resp := storeAndGetLfs(t, &content, nil, http.StatusOK) + checkResponseTestContentEncoding(t, &content, resp, false) +} + +func TestGetLFSRangeNo(t *testing.T) { + defer tests.PrepareTestEnv(t)() + content := []byte("123456789\n") + + resp := storeAndGetLfs(t, &content, nil, http.StatusOK) + assert.Equal(t, content, resp.Body.Bytes()) +} + +func TestGetLFSRange(t *testing.T) { + defer tests.PrepareTestEnv(t)() + content := []byte("123456789\n") + + tests := []struct { + in string + out string + status int + }{ + {"bytes=0-0", "1", http.StatusPartialContent}, + {"bytes=0-1", "12", http.StatusPartialContent}, + {"bytes=1-1", "2", http.StatusPartialContent}, + {"bytes=1-3", "234", http.StatusPartialContent}, + {"bytes=1-", "23456789\n", http.StatusPartialContent}, + // end-range smaller than start-range is ignored + {"bytes=1-0", "23456789\n", http.StatusPartialContent}, + {"bytes=0-10", "123456789\n", http.StatusPartialContent}, + // end-range bigger than length-1 is ignored + {"bytes=0-11", "123456789\n", http.StatusPartialContent}, + {"bytes=11-", "Requested Range Not Satisfiable", http.StatusRequestedRangeNotSatisfiable}, + // incorrect header value cause whole header to be ignored + {"bytes=-", "123456789\n", http.StatusOK}, + {"foobar", "123456789\n", http.StatusOK}, + } + + for _, tt := range tests { + t.Run(tt.in, func(t *testing.T) { + h := http.Header{ + "Range": []string{tt.in}, + } + resp := storeAndGetLfs(t, &content, &h, tt.status) + if tt.status == http.StatusPartialContent || tt.status == http.StatusOK { + assert.Equal(t, tt.out, resp.Body.String()) + } else { + var er lfs.ErrorResponse + err := json.Unmarshal(resp.Body.Bytes(), &er) + assert.NoError(t, err) + assert.Equal(t, tt.out, er.Message) + } + }) + } +} diff --git a/tests/integration/lfs_local_endpoint_test.go b/tests/integration/lfs_local_endpoint_test.go new file mode 100644 index 0000000000..88c08c63db --- /dev/null +++ b/tests/integration/lfs_local_endpoint_test.go @@ -0,0 +1,117 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/url" + "os" + "path/filepath" + "testing" + + "code.gitea.io/gitea/modules/lfs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func str2url(raw string) *url.URL { + u, _ := url.Parse(raw) + return u +} + +func TestDetermineLocalEndpoint(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + root, _ := os.MkdirTemp("", "lfs_test") + defer os.RemoveAll(root) + + rootdotgit, _ := os.MkdirTemp("", "lfs_test") + defer os.RemoveAll(rootdotgit) + os.Mkdir(filepath.Join(rootdotgit, ".git"), 0o700) + + lfsroot, _ := os.MkdirTemp("", "lfs_test") + defer os.RemoveAll(lfsroot) + + // Test cases + cases := []struct { + cloneurl string + lfsurl string + expected *url.URL + }{ + // case 0 + { + cloneurl: root, + lfsurl: "", + expected: str2url(fmt.Sprintf("file://%s", root)), + }, + // case 1 + { + cloneurl: root, + lfsurl: lfsroot, + expected: str2url(fmt.Sprintf("file://%s", lfsroot)), + }, + // case 2 + { + cloneurl: "https://git.com/repo.git", + lfsurl: lfsroot, + expected: str2url(fmt.Sprintf("file://%s", lfsroot)), + }, + // case 3 + { + cloneurl: rootdotgit, + lfsurl: "", + expected: str2url(fmt.Sprintf("file://%s", filepath.Join(rootdotgit, ".git"))), + }, + // case 4 + { + cloneurl: "", + lfsurl: rootdotgit, + expected: str2url(fmt.Sprintf("file://%s", filepath.Join(rootdotgit, ".git"))), + }, + // case 5 + { + cloneurl: rootdotgit, + lfsurl: rootdotgit, + expected: str2url(fmt.Sprintf("file://%s", filepath.Join(rootdotgit, ".git"))), + }, + // case 6 + { + cloneurl: fmt.Sprintf("file://%s", root), + lfsurl: "", + expected: str2url(fmt.Sprintf("file://%s", root)), + }, + // case 7 + { + cloneurl: fmt.Sprintf("file://%s", root), + lfsurl: fmt.Sprintf("file://%s", lfsroot), + expected: str2url(fmt.Sprintf("file://%s", lfsroot)), + }, + // case 8 + { + cloneurl: root, + lfsurl: fmt.Sprintf("file://%s", lfsroot), + expected: str2url(fmt.Sprintf("file://%s", lfsroot)), + }, + // case 9 + { + cloneurl: "", + lfsurl: "/does/not/exist", + expected: nil, + }, + // case 10 + { + cloneurl: "", + lfsurl: "file:///does/not/exist", + expected: str2url("file:///does/not/exist"), + }, + } + + for n, c := range cases { + ep := lfs.DetermineEndpoint(c.cloneurl, c.lfsurl) + + assert.Equal(t, c.expected, ep, "case %d: error should match", n) + } +} diff --git a/tests/integration/links_test.go b/tests/integration/links_test.go new file mode 100644 index 0000000000..4eb29f0cee --- /dev/null +++ b/tests/integration/links_test.go @@ -0,0 +1,176 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "path" + "testing" + + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestLinksNoLogin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + links := []string{ + "/explore/repos", + "/explore/repos?q=test", + "/explore/users", + "/explore/users?q=test", + "/explore/organizations", + "/explore/organizations?q=test", + "/", + "/user/sign_up", + "/user/login", + "/user/forgot_password", + "/api/swagger", + "/user2/repo1", + "/user2/repo1/", + "/user2/repo1/projects", + "/user2/repo1/projects/1", + "/assets/img/404.png", + "/assets/img/500.png", + } + + for _, link := range links { + req := NewRequest(t, "GET", link) + MakeRequest(t, req, http.StatusOK) + } +} + +func TestRedirectsNoLogin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + redirects := map[string]string{ + "/user2/repo1/commits/master": "/user2/repo1/commits/branch/master", + "/user2/repo1/src/master": "/user2/repo1/src/branch/master", + "/user2/repo1/src/master/file.txt": "/user2/repo1/src/branch/master/file.txt", + "/user2/repo1/src/master/directory/file.txt": "/user2/repo1/src/branch/master/directory/file.txt", + "/user/avatar/Ghost/-1": "/assets/img/avatar_default.png", + "/api/v1/swagger": "/api/swagger", + } + for link, redirectLink := range redirects { + req := NewRequest(t, "GET", link) + resp := MakeRequest(t, req, http.StatusSeeOther) + assert.EqualValues(t, path.Join(setting.AppSubURL, redirectLink), test.RedirectURL(resp)) + } +} + +func TestNoLoginNotExist(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + links := []string{ + "/user5/repo4/projects", + "/user5/repo4/projects/3", + } + + for _, link := range links { + req := NewRequest(t, "GET", link) + MakeRequest(t, req, http.StatusNotFound) + } +} + +func testLinksAsUser(userName string, t *testing.T) { + links := []string{ + "/explore/repos", + "/explore/repos?q=test", + "/explore/users", + "/explore/users?q=test", + "/explore/organizations", + "/explore/organizations?q=test", + "/", + "/user/forgot_password", + "/api/swagger", + "/issues", + "/issues?type=your_repositories&repos=[0]&sort=&state=open", + "/issues?type=assigned&repos=[0]&sort=&state=open", + "/issues?type=your_repositories&repos=[0]&sort=&state=closed", + "/issues?type=assigned&repos=[]&sort=&state=closed", + "/issues?type=assigned&sort=&state=open", + "/issues?type=created_by&repos=[1,2]&sort=&state=closed", + "/issues?type=created_by&repos=[1,2]&sort=&state=open", + "/pulls", + "/pulls?type=your_repositories&repos=[2]&sort=&state=open", + "/pulls?type=assigned&repos=[]&sort=&state=open", + "/pulls?type=created_by&repos=[0]&sort=&state=open", + "/pulls?type=your_repositories&repos=[0]&sort=&state=closed", + "/pulls?type=assigned&repos=[0]&sort=&state=closed", + "/pulls?type=created_by&repos=[0]&sort=&state=closed", + "/milestones", + "/milestones?sort=mostcomplete&state=closed", + "/milestones?type=your_repositories&sort=mostcomplete&state=closed", + "/milestones?sort=&repos=[1]&state=closed", + "/milestones?sort=&repos=[1]&state=open", + "/milestones?repos=[0]&sort=mostissues&state=open", + "/notifications", + "/repo/create", + "/repo/migrate", + "/org/create", + "/user2", + "/user2?tab=stars", + "/user2?tab=activity", + "/user/settings", + "/user/settings/account", + "/user/settings/security", + "/user/settings/security/two_factor/enroll", + "/user/settings/keys", + "/user/settings/organization", + "/user/settings/repos", + } + + session := loginUser(t, userName) + for _, link := range links { + req := NewRequest(t, "GET", link) + session.MakeRequest(t, req, http.StatusOK) + } + + reqAPI := NewRequestf(t, "GET", "/api/v1/users/%s/repos", userName) + respAPI := MakeRequest(t, reqAPI, http.StatusOK) + + var apiRepos []*api.Repository + DecodeJSON(t, respAPI, &apiRepos) + + repoLinks := []string{ + "", + "/issues", + "/pulls", + "/commits/branch/master", + "/graph", + "/settings", + "/settings/collaboration", + "/settings/branches", + "/settings/hooks", + // FIXME: below links should return 200 but 404 ?? + //"/settings/hooks/git", + //"/settings/hooks/git/pre-receive", + //"/settings/hooks/git/update", + //"/settings/hooks/git/post-receive", + "/settings/keys", + "/releases", + "/releases/new", + //"/wiki/_pages", + "/wiki/?action=_new", + } + + for _, repo := range apiRepos { + for _, link := range repoLinks { + req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s%s", userName, repo.Name, link)) + session.MakeRequest(t, req, http.StatusOK) + } + } +} + +func TestLinksLogin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + testLinksAsUser("user2", t) +} diff --git a/tests/integration/migrate_test.go b/tests/integration/migrate_test.go new file mode 100644 index 0000000000..0fe4014344 --- /dev/null +++ b/tests/integration/migrate_test.go @@ -0,0 +1,98 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "os" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/services/migrations" + + "github.com/stretchr/testify/assert" +) + +func TestMigrateLocalPath(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"}) + + old := setting.ImportLocalPaths + setting.ImportLocalPaths = true + + lowercasePath, err := os.MkdirTemp("", "lowercase") // may not be lowercase because MkdirTemp creates a random directory name which may be mixedcase + assert.NoError(t, err) + defer os.RemoveAll(lowercasePath) + + err = migrations.IsMigrateURLAllowed(lowercasePath, adminUser) + assert.NoError(t, err, "case lowercase path") + + mixedcasePath, err := os.MkdirTemp("", "mIxeDCaSe") + assert.NoError(t, err) + defer os.RemoveAll(mixedcasePath) + + err = migrations.IsMigrateURLAllowed(mixedcasePath, adminUser) + assert.NoError(t, err, "case mixedcase path") + + setting.ImportLocalPaths = old +} + +func TestMigrateGiteaForm(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + AllowLocalNetworks := setting.Migrations.AllowLocalNetworks + setting.Migrations.AllowLocalNetworks = true + AppVer := setting.AppVer + // Gitea SDK (go-sdk) need to parse the AppVer from server response, so we must set it to a valid version string. + setting.AppVer = "1.16.0" + defer func() { + setting.Migrations.AllowLocalNetworks = AllowLocalNetworks + setting.AppVer = AppVer + migrations.Init() + }() + assert.NoError(t, migrations.Init()) + + ownerName := "user2" + repoName := "repo1" + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: ownerName}) + session := loginUser(t, ownerName) + token := getTokenForLoggedInUser(t, session) + + // Step 0: verify the repo is available + req := NewRequestf(t, "GET", fmt.Sprintf("/%s/%s", ownerName, repoName)) + _ = session.MakeRequest(t, req, http.StatusOK) + // Step 1: get the Gitea migration form + req = NewRequestf(t, "GET", "/repo/migrate/?service_type=%d", structs.GiteaService) + resp := session.MakeRequest(t, req, http.StatusOK) + // Step 2: load the form + htmlDoc := NewHTMLParser(t, resp.Body) + link, exists := htmlDoc.doc.Find(`form.ui.form[action^="/repo/migrate"]`).Attr("action") + assert.True(t, exists, "The template has changed") + // Step 4: submit the migration to only migrate issues + migratedRepoName := "otherrepo" + req = NewRequestWithValues(t, "POST", link, map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "service": fmt.Sprintf("%d", structs.GiteaService), + "clone_addr": fmt.Sprintf("%s%s/%s", u, ownerName, repoName), + "auth_token": token, + "issues": "on", + "repo_name": migratedRepoName, + "description": "", + "uid": fmt.Sprintf("%d", repoOwner.ID), + }) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + // Step 5: a redirection displays the migrated repository + loc := resp.Header().Get("Location") + assert.EqualValues(t, fmt.Sprintf("/%s/%s", ownerName, migratedRepoName), loc) + // Step 6: check the repo was created + unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: migratedRepoName}) + }) +} diff --git a/tests/integration/migration-test/gitea-v1.6.4.mssql.sql.gz b/tests/integration/migration-test/gitea-v1.6.4.mssql.sql.gz Binary files differnew file mode 100644 index 0000000000..1b676feda1 --- /dev/null +++ b/tests/integration/migration-test/gitea-v1.6.4.mssql.sql.gz diff --git a/tests/integration/migration-test/gitea-v1.6.4.mysql.sql.gz b/tests/integration/migration-test/gitea-v1.6.4.mysql.sql.gz Binary files differnew file mode 100644 index 0000000000..30cca8b382 --- /dev/null +++ b/tests/integration/migration-test/gitea-v1.6.4.mysql.sql.gz diff --git a/tests/integration/migration-test/gitea-v1.6.4.postgres.sql.gz b/tests/integration/migration-test/gitea-v1.6.4.postgres.sql.gz Binary files differnew file mode 100644 index 0000000000..bd66f6ba4f --- /dev/null +++ b/tests/integration/migration-test/gitea-v1.6.4.postgres.sql.gz diff --git a/tests/integration/migration-test/gitea-v1.6.4.sqlite3.sql.gz b/tests/integration/migration-test/gitea-v1.6.4.sqlite3.sql.gz Binary files differnew file mode 100644 index 0000000000..a777c53025 --- /dev/null +++ b/tests/integration/migration-test/gitea-v1.6.4.sqlite3.sql.gz diff --git a/tests/integration/migration-test/gitea-v1.7.0.mssql.sql.gz b/tests/integration/migration-test/gitea-v1.7.0.mssql.sql.gz Binary files differnew file mode 100644 index 0000000000..bd869cfa58 --- /dev/null +++ b/tests/integration/migration-test/gitea-v1.7.0.mssql.sql.gz diff --git a/tests/integration/migration-test/gitea-v1.7.0.mysql.sql.gz b/tests/integration/migration-test/gitea-v1.7.0.mysql.sql.gz Binary files differnew file mode 100644 index 0000000000..d0ab10891c --- /dev/null +++ b/tests/integration/migration-test/gitea-v1.7.0.mysql.sql.gz diff --git a/tests/integration/migration-test/gitea-v1.7.0.postgres.sql.gz b/tests/integration/migration-test/gitea-v1.7.0.postgres.sql.gz Binary files differnew file mode 100644 index 0000000000..e4716c6b43 --- /dev/null +++ b/tests/integration/migration-test/gitea-v1.7.0.postgres.sql.gz diff --git a/tests/integration/migration-test/gitea-v1.7.0.sqlite3.sql.gz b/tests/integration/migration-test/gitea-v1.7.0.sqlite3.sql.gz Binary files differnew file mode 100644 index 0000000000..3155249b07 --- /dev/null +++ b/tests/integration/migration-test/gitea-v1.7.0.sqlite3.sql.gz diff --git a/tests/integration/migration-test/migration_test.go b/tests/integration/migration-test/migration_test.go new file mode 100644 index 0000000000..b631168340 --- /dev/null +++ b/tests/integration/migration-test/migration_test.go @@ -0,0 +1,338 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "compress/gzip" + "context" + "database/sql" + "fmt" + "io" + "os" + "path" + "path/filepath" + "regexp" + "sort" + "strings" + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/migrations" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/charset" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" + "xorm.io/xorm" +) + +var currentEngine *xorm.Engine + +func initMigrationTest(t *testing.T) func() { + deferFn := tests.PrintCurrentTest(t, 2) + giteaRoot := base.SetupGiteaRoot() + if giteaRoot == "" { + tests.Printf("Environment variable $GITEA_ROOT not set\n") + os.Exit(1) + } + setting.AppPath = path.Join(giteaRoot, "gitea") + if _, err := os.Stat(setting.AppPath); err != nil { + tests.Printf("Could not find gitea binary at %s\n", setting.AppPath) + os.Exit(1) + } + + giteaConf := os.Getenv("GITEA_CONF") + if giteaConf == "" { + tests.Printf("Environment variable $GITEA_CONF not set\n") + os.Exit(1) + } else if !path.IsAbs(giteaConf) { + setting.CustomConf = path.Join(giteaRoot, giteaConf) + } else { + setting.CustomConf = giteaConf + } + + setting.LoadForTest() + + assert.True(t, len(setting.RepoRootPath) != 0) + assert.NoError(t, util.RemoveAll(setting.RepoRootPath)) + assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) + ownerDirs, err := os.ReadDir(setting.RepoRootPath) + if err != nil { + assert.NoError(t, err, "unable to read the new repo root: %v\n", err) + } + for _, ownerDir := range ownerDirs { + if !ownerDir.Type().IsDir() { + continue + } + repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name())) + if err != nil { + assert.NoError(t, err, "unable to read the new repo root: %v\n", err) + } + for _, repoDir := range repoDirs { + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0o755) + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0o755) + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0o755) + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0o755) + } + } + + assert.NoError(t, git.InitFull(context.Background())) + setting.InitDBConfig() + setting.NewLogServices(true) + return deferFn +} + +func availableVersions() ([]string, error) { + migrationsDir, err := os.Open("tests/integration/migration-test") + if err != nil { + return nil, err + } + defer migrationsDir.Close() + versionRE, err := regexp.Compile("gitea-v(?P<version>.+)\\." + regexp.QuoteMeta(setting.Database.Type) + "\\.sql.gz") + if err != nil { + return nil, err + } + + filenames, err := migrationsDir.Readdirnames(-1) + if err != nil { + return nil, err + } + versions := []string{} + for _, filename := range filenames { + if versionRE.MatchString(filename) { + substrings := versionRE.FindStringSubmatch(filename) + versions = append(versions, substrings[1]) + } + } + sort.Strings(versions) + return versions, nil +} + +func readSQLFromFile(version string) (string, error) { + filename := fmt.Sprintf("tests/integration/migration-test/gitea-v%s.%s.sql.gz", version, setting.Database.Type) + + if _, err := os.Stat(filename); os.IsNotExist(err) { + return "", nil + } + + file, err := os.Open(filename) + if err != nil { + return "", err + } + defer file.Close() + + gr, err := gzip.NewReader(file) + if err != nil { + return "", err + } + defer gr.Close() + + bytes, err := io.ReadAll(gr) + if err != nil { + return "", err + } + return string(charset.RemoveBOMIfPresent(bytes)), nil +} + +func restoreOldDB(t *testing.T, version string) bool { + data, err := readSQLFromFile(version) + assert.NoError(t, err) + if len(data) == 0 { + tests.Printf("No db found to restore for %s version: %s\n", setting.Database.Type, version) + return false + } + + switch { + case setting.Database.UseSQLite3: + util.Remove(setting.Database.Path) + err := os.MkdirAll(path.Dir(setting.Database.Path), os.ModePerm) + assert.NoError(t, err) + + db, err := sql.Open("sqlite3", fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d&_txlock=immediate", setting.Database.Path, setting.Database.Timeout)) + assert.NoError(t, err) + defer db.Close() + + _, err = db.Exec(data) + assert.NoError(t, err) + db.Close() + + case setting.Database.UseMySQL: + db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/", + setting.Database.User, setting.Database.Passwd, setting.Database.Host)) + assert.NoError(t, err) + defer db.Close() + + _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)) + assert.NoError(t, err) + + _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)) + assert.NoError(t, err) + db.Close() + + db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?multiStatements=true", + setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name)) + assert.NoError(t, err) + defer db.Close() + + _, err = db.Exec(data) + assert.NoError(t, err) + db.Close() + + case setting.Database.UsePostgreSQL: + var db *sql.DB + var err error + if setting.Database.Host[0] == '/' { + db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@/?sslmode=%s&host=%s", + setting.Database.User, setting.Database.Passwd, setting.Database.SSLMode, setting.Database.Host)) + assert.NoError(t, err) + } else { + db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/?sslmode=%s", + setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.SSLMode)) + assert.NoError(t, err) + } + defer db.Close() + + _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)) + assert.NoError(t, err) + + _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)) + assert.NoError(t, err) + db.Close() + + // Check if we need to setup a specific schema + if len(setting.Database.Schema) != 0 { + if setting.Database.Host[0] == '/' { + db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@/%s?sslmode=%s&host=%s", + setting.Database.User, setting.Database.Passwd, setting.Database.Name, setting.Database.SSLMode, setting.Database.Host)) + } else { + db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", + setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode)) + } + if !assert.NoError(t, err) { + return false + } + defer db.Close() + + schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema)) + if !assert.NoError(t, err) || !assert.NotEmpty(t, schrows) { + return false + } + + if !schrows.Next() { + // Create and setup a DB schema + _, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema)) + assert.NoError(t, err) + } + schrows.Close() + + // Make the user's default search path the created schema; this will affect new connections + _, err = db.Exec(fmt.Sprintf(`ALTER USER "%s" SET search_path = %s`, setting.Database.User, setting.Database.Schema)) + assert.NoError(t, err) + + db.Close() + } + + if setting.Database.Host[0] == '/' { + db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@/%s?sslmode=%s&host=%s", + setting.Database.User, setting.Database.Passwd, setting.Database.Name, setting.Database.SSLMode, setting.Database.Host)) + } else { + db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", + setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode)) + } + assert.NoError(t, err) + defer db.Close() + + _, err = db.Exec(data) + assert.NoError(t, err) + db.Close() + + case setting.Database.UseMSSQL: + host, port := setting.ParseMSSQLHostPort(setting.Database.Host) + db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", + host, port, "master", setting.Database.User, setting.Database.Passwd)) + assert.NoError(t, err) + defer db.Close() + + _, err = db.Exec("DROP DATABASE IF EXISTS [gitea]") + assert.NoError(t, err) + + statements := strings.Split(data, "\nGO\n") + for _, statement := range statements { + if len(statement) > 5 && statement[:5] == "USE [" { + dbname := statement[5 : len(statement)-1] + db.Close() + db, err = sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", + host, port, dbname, setting.Database.User, setting.Database.Passwd)) + assert.NoError(t, err) + defer db.Close() + } + _, err = db.Exec(statement) + assert.NoError(t, err, "Failure whilst running: %s\nError: %v", statement, err) + } + db.Close() + } + return true +} + +func wrappedMigrate(x *xorm.Engine) error { + currentEngine = x + return migrations.Migrate(x) +} + +func doMigrationTest(t *testing.T, version string) { + defer tests.PrintCurrentTest(t)() + tests.Printf("Performing migration test for %s version: %s\n", setting.Database.Type, version) + if !restoreOldDB(t, version) { + return + } + + setting.NewXORMLogService(false) + + err := db.InitEngineWithMigration(context.Background(), wrappedMigrate) + assert.NoError(t, err) + currentEngine.Close() + + beans, _ := db.NamesToBean() + + err = db.InitEngineWithMigration(context.Background(), func(x *xorm.Engine) error { + currentEngine = x + return migrations.RecreateTables(beans...)(x) + }) + assert.NoError(t, err) + currentEngine.Close() + + // We do this a second time to ensure that there is not a problem with retained indices + err = db.InitEngineWithMigration(context.Background(), func(x *xorm.Engine) error { + currentEngine = x + return migrations.RecreateTables(beans...)(x) + }) + assert.NoError(t, err) + + currentEngine.Close() +} + +func TestMigrations(t *testing.T) { + defer initMigrationTest(t)() + + dialect := setting.Database.Type + versions, err := availableVersions() + assert.NoError(t, err) + + if len(versions) == 0 { + tests.Printf("No old database versions available to migration test for %s\n", dialect) + return + } + + tests.Printf("Preparing to test %d migrations for %s\n", len(versions), dialect) + for _, version := range versions { + t.Run(fmt.Sprintf("Migrate-%s-%s", dialect, version), func(t *testing.T) { + doMigrationTest(t, version) + }) + } +} diff --git a/tests/integration/mirror_pull_test.go b/tests/integration/mirror_pull_test.go new file mode 100644 index 0000000000..707cf46fa0 --- /dev/null +++ b/tests/integration/mirror_pull_test.go @@ -0,0 +1,98 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "context" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/migration" + "code.gitea.io/gitea/modules/repository" + mirror_service "code.gitea.io/gitea/services/mirror" + release_service "code.gitea.io/gitea/services/release" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestMirrorPull(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + repoPath := repo_model.RepoPath(user.Name, repo.Name) + + opts := migration.MigrateOptions{ + RepoName: "test_mirror", + Description: "Test mirror", + Private: false, + Mirror: true, + CloneAddr: repoPath, + Wiki: true, + Releases: false, + } + + mirrorRepo, err := repository.CreateRepository(user, user, repository.CreateRepoOptions{ + Name: opts.RepoName, + Description: opts.Description, + IsPrivate: opts.Private, + IsMirror: opts.Mirror, + Status: repo_model.RepositoryBeingMigrated, + }) + assert.NoError(t, err) + assert.True(t, mirrorRepo.IsMirror, "expected pull-mirror repo to be marked as a mirror immediately after its creation") + + ctx := context.Background() + + mirror, err := repository.MigrateRepositoryGitData(ctx, user, mirrorRepo, opts, nil) + assert.NoError(t, err) + + gitRepo, err := git.OpenRepository(git.DefaultContext, repoPath) + assert.NoError(t, err) + defer gitRepo.Close() + + findOptions := repo_model.FindReleasesOptions{IncludeDrafts: true, IncludeTags: true} + initCount, err := repo_model.GetReleaseCountByRepoID(mirror.ID, findOptions) + assert.NoError(t, err) + + assert.NoError(t, release_service.CreateRelease(gitRepo, &repo_model.Release{ + RepoID: repo.ID, + Repo: repo, + PublisherID: user.ID, + Publisher: user, + TagName: "v0.2", + Target: "master", + Title: "v0.2 is released", + Note: "v0.2 is released", + IsDraft: false, + IsPrerelease: false, + IsTag: true, + }, nil, "")) + + _, err = repo_model.GetMirrorByRepoID(ctx, mirror.ID) + assert.NoError(t, err) + + ok := mirror_service.SyncPullMirror(ctx, mirror.ID) + assert.True(t, ok) + + count, err := repo_model.GetReleaseCountByRepoID(mirror.ID, findOptions) + assert.NoError(t, err) + assert.EqualValues(t, initCount+1, count) + + release, err := repo_model.GetRelease(repo.ID, "v0.2") + assert.NoError(t, err) + assert.NoError(t, release_service.DeleteReleaseByID(ctx, release.ID, user, true)) + + ok = mirror_service.SyncPullMirror(ctx, mirror.ID) + assert.True(t, ok) + + count, err = repo_model.GetReleaseCountByRepoID(mirror.ID, findOptions) + assert.NoError(t, err) + assert.EqualValues(t, initCount, count) +} diff --git a/tests/integration/mirror_push_test.go b/tests/integration/mirror_push_test.go new file mode 100644 index 0000000000..f2adf5f5a3 --- /dev/null +++ b/tests/integration/mirror_push_test.go @@ -0,0 +1,120 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strconv" + "testing" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/migrations" + mirror_service "code.gitea.io/gitea/services/mirror" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestMirrorPush(t *testing.T) { + onGiteaRun(t, testMirrorPush) +} + +func testMirrorPush(t *testing.T, u *url.URL) { + defer tests.PrepareTestEnv(t)() + + setting.Migrations.AllowLocalNetworks = true + assert.NoError(t, migrations.Init()) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + srcRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + mirrorRepo, err := repository.CreateRepository(user, user, repository.CreateRepoOptions{ + Name: "test-push-mirror", + }) + assert.NoError(t, err) + + ctx := NewAPITestContext(t, user.LowerName, srcRepo.Name) + + doCreatePushMirror(ctx, fmt.Sprintf("%s%s/%s", u.String(), url.PathEscape(ctx.Username), url.PathEscape(mirrorRepo.Name)), user.LowerName, userPassword)(t) + + mirrors, _, err := repo_model.GetPushMirrorsByRepoID(db.DefaultContext, srcRepo.ID, db.ListOptions{}) + assert.NoError(t, err) + assert.Len(t, mirrors, 1) + + ok := mirror_service.SyncPushMirror(context.Background(), mirrors[0].ID) + assert.True(t, ok) + + srcGitRepo, err := git.OpenRepository(git.DefaultContext, srcRepo.RepoPath()) + assert.NoError(t, err) + defer srcGitRepo.Close() + + srcCommit, err := srcGitRepo.GetBranchCommit("master") + assert.NoError(t, err) + + mirrorGitRepo, err := git.OpenRepository(git.DefaultContext, mirrorRepo.RepoPath()) + assert.NoError(t, err) + defer mirrorGitRepo.Close() + + mirrorCommit, err := mirrorGitRepo.GetBranchCommit("master") + assert.NoError(t, err) + + assert.Equal(t, srcCommit.ID, mirrorCommit.ID) + + // Cleanup + doRemovePushMirror(ctx, fmt.Sprintf("%s%s/%s", u.String(), url.PathEscape(ctx.Username), url.PathEscape(mirrorRepo.Name)), user.LowerName, userPassword, int(mirrors[0].ID))(t) + mirrors, _, err = repo_model.GetPushMirrorsByRepoID(db.DefaultContext, srcRepo.ID, db.ListOptions{}) + assert.NoError(t, err) + assert.Len(t, mirrors, 0) +} + +func doCreatePushMirror(ctx APITestContext, address, username, password string) func(t *testing.T) { + return func(t *testing.T) { + csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/settings", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame))) + + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), map[string]string{ + "_csrf": csrf, + "action": "push-mirror-add", + "push_mirror_address": address, + "push_mirror_username": username, + "push_mirror_password": password, + "push_mirror_interval": "0", + }) + ctx.Session.MakeRequest(t, req, http.StatusSeeOther) + + flashCookie := ctx.Session.GetCookie("macaron_flash") + assert.NotNil(t, flashCookie) + assert.Contains(t, flashCookie.Value, "success") + } +} + +func doRemovePushMirror(ctx APITestContext, address, username, password string, pushMirrorID int) func(t *testing.T) { + return func(t *testing.T) { + csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/settings", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame))) + + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), map[string]string{ + "_csrf": csrf, + "action": "push-mirror-remove", + "push_mirror_id": strconv.Itoa(pushMirrorID), + "push_mirror_address": address, + "push_mirror_username": username, + "push_mirror_password": password, + "push_mirror_interval": "0", + }) + ctx.Session.MakeRequest(t, req, http.StatusSeeOther) + + flashCookie := ctx.Session.GetCookie("macaron_flash") + assert.NotNil(t, flashCookie) + assert.Contains(t, flashCookie.Value, "success") + } +} diff --git a/tests/integration/nonascii_branches_test.go b/tests/integration/nonascii_branches_test.go new file mode 100644 index 0000000000..ae69506f1b --- /dev/null +++ b/tests/integration/nonascii_branches_test.go @@ -0,0 +1,214 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "path" + "testing" + + "code.gitea.io/gitea/tests" + "github.com/stretchr/testify/assert" +) + +func testSrcRouteRedirect(t *testing.T, session *TestSession, user, repo, route, expectedLocation string, expectedStatus int) { + prefix := path.Join("/", user, repo, "src") + + // Make request + req := NewRequest(t, "GET", path.Join(prefix, route)) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + + // Check Location header + location := resp.HeaderMap.Get("Location") + assert.Equal(t, path.Join(prefix, expectedLocation), location) + + // Perform redirect + req = NewRequest(t, "GET", location) + session.MakeRequest(t, req, expectedStatus) +} + +func setDefaultBranch(t *testing.T, session *TestSession, user, repo, branch string) { + location := path.Join("/", user, repo, "settings/branches") + csrf := GetCSRF(t, session, location) + req := NewRequestWithValues(t, "POST", location, map[string]string{ + "_csrf": csrf, + "action": "default_branch", + "branch": branch, + }) + session.MakeRequest(t, req, http.StatusSeeOther) +} + +func TestNonasciiBranches(t *testing.T) { + testRedirects := []struct { + from string + to string + status int + }{ + // Branches + { + from: "master", + to: "branch/master", + status: http.StatusOK, + }, + { + from: "master/README.md", + to: "branch/master/README.md", + status: http.StatusOK, + }, + { + from: "master/badfile", + to: "branch/master/badfile", + status: http.StatusNotFound, // it does not exists + }, + { + from: "ГлавнаÑВетка", + to: "branch/%D0%93%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F%D0%92%D0%B5%D1%82%D0%BA%D0%B0", + status: http.StatusOK, + }, + { + from: "а/б/в", + to: "branch/%D0%B0/%D0%B1/%D0%B2", + status: http.StatusOK, + }, + { + from: "Grüßen/README.md", + to: "branch/Gr%C3%BC%C3%9Fen/README.md", + status: http.StatusOK, + }, + { + from: "Plus+Is+Not+Space", + to: "branch/Plus+Is+Not+Space", + status: http.StatusOK, + }, + { + from: "Plus+Is+Not+Space/Файл.md", + to: "branch/Plus+Is+Not+Space/%D0%A4%D0%B0%D0%B9%D0%BB.md", + status: http.StatusOK, + }, + { + from: "Plus+Is+Not+Space/and+it+is+valid.md", + to: "branch/Plus+Is+Not+Space/and+it+is+valid.md", + status: http.StatusOK, + }, + { + from: "ブランãƒ", + to: "branch/%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81", + status: http.StatusOK, + }, + // Tags + { + from: "ТÑг", + to: "tag/%D0%A2%D1%8D%D0%B3", + status: http.StatusOK, + }, + { + from: "Ð/人", + to: "tag/%D0%81/%E4%BA%BA", + status: http.StatusOK, + }, + { + from: "ã‚¿ã‚°", + to: "tag/%E3%82%BF%E3%82%B0", + status: http.StatusOK, + }, + { + from: "ã‚¿ã‚°/ファイル.md", + to: "tag/%E3%82%BF%E3%82%B0/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.md", + status: http.StatusOK, + }, + // Files + { + from: "README.md", + to: "branch/Plus+Is+Not+Space/README.md", + status: http.StatusOK, + }, + { + from: "Файл.md", + to: "branch/Plus+Is+Not+Space/%D0%A4%D0%B0%D0%B9%D0%BB.md", + status: http.StatusOK, + }, + { + from: "ファイル.md", + to: "branch/Plus+Is+Not+Space/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.md", + status: http.StatusNotFound, // it's not on default branch + }, + // Same but url-encoded (few tests) + { + from: "%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81", + to: "branch/%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81", + status: http.StatusOK, + }, + { + from: "%E3%82%BF%E3%82%b0", + to: "tag/%E3%82%BF%E3%82%B0", + status: http.StatusOK, + }, + { + from: "%D0%A4%D0%B0%D0%B9%D0%BB.md", + to: "branch/Plus+Is+Not+Space/%D0%A4%D0%B0%D0%B9%D0%BB.md", + status: http.StatusOK, + }, + { + from: "%D0%81%2F%E4%BA%BA", + to: "tag/%D0%81/%E4%BA%BA", + status: http.StatusOK, + }, + { + from: "Ð%2F%E4%BA%BA", + to: "tag/%D0%81/%E4%BA%BA", + status: http.StatusOK, + }, + { + from: "Plus+Is+Not+Space/%25%252525mightnotplaywell", + to: "branch/Plus+Is+Not+Space/%25%252525mightnotplaywell", + status: http.StatusOK, + }, + { + from: "Plus+Is+Not+Space/%25253Fisnotaquestion%25253F", + to: "branch/Plus+Is+Not+Space/%25253Fisnotaquestion%25253F", + status: http.StatusOK, + }, + { + from: "Plus+Is+Not+Space/" + url.PathEscape("%3Fis?and#afile"), + to: "branch/Plus+Is+Not+Space/" + url.PathEscape("%3Fis?and#afile"), + status: http.StatusOK, + }, + { + from: "Plus+Is+Not+Space/10%25.md", + to: "branch/Plus+Is+Not+Space/10%25.md", + status: http.StatusOK, + }, + { + from: "Plus+Is+Not+Space/" + url.PathEscape("This+file%20has 1space"), + to: "branch/Plus+Is+Not+Space/" + url.PathEscape("This+file%20has 1space"), + status: http.StatusOK, + }, + { + from: "Plus+Is+Not+Space/" + url.PathEscape("This+file%2520has 2 spaces"), + to: "branch/Plus+Is+Not+Space/" + url.PathEscape("This+file%2520has 2 spaces"), + status: http.StatusOK, + }, + { + from: "Plus+Is+Not+Space/" + url.PathEscape("£15&$6.txt"), + to: "branch/Plus+Is+Not+Space/" + url.PathEscape("£15&$6.txt"), + status: http.StatusOK, + }, + } + + defer tests.PrepareTestEnv(t)() + + user := "user2" + repo := "utf8" + session := loginUser(t, user) + + setDefaultBranch(t, session, user, repo, "Plus+Is+Not+Space") + + for _, test := range testRedirects { + testSrcRouteRedirect(t, session, user, repo, test.from, test.to, test.status) + } + + setDefaultBranch(t, session, user, repo, "master") +} diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go new file mode 100644 index 0000000000..7fa26c8147 --- /dev/null +++ b/tests/integration/oauth_test.go @@ -0,0 +1,261 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "bytes" + "io" + "net/http" + "testing" + + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +const defaultAuthorize = "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate" + +func TestNoClientID(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequest(t, "GET", "/login/oauth/authorize") + ctx := loginUser(t, "user2") + ctx.MakeRequest(t, req, http.StatusBadRequest) +} + +func TestLoginRedirect(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequest(t, "GET", "/login/oauth/authorize") + assert.Contains(t, MakeRequest(t, req, http.StatusSeeOther).Body.String(), "/user/login") +} + +func TestShowAuthorize(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequest(t, "GET", defaultAuthorize) + ctx := loginUser(t, "user4") + resp := ctx.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + htmlDoc.AssertElement(t, "#authorize-app", true) + htmlDoc.GetCSRF() +} + +func TestRedirectWithExistingGrant(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequest(t, "GET", defaultAuthorize) + ctx := loginUser(t, "user1") + resp := ctx.MakeRequest(t, req, http.StatusSeeOther) + u, err := resp.Result().Location() + assert.NoError(t, err) + assert.Equal(t, "thestate", u.Query().Get("state")) + assert.Truef(t, len(u.Query().Get("code")) > 30, "authorization code '%s' should be longer then 30", u.Query().Get("code")) +} + +func TestAccessTokenExchange(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ + "grant_type": "authorization_code", + "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138", + "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=", + "redirect_uri": "a", + "code": "authcode", + "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally + }) + resp := MakeRequest(t, req, http.StatusOK) + type response struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int64 `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + } + parsed := new(response) + + assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed)) + assert.True(t, len(parsed.AccessToken) > 10) + assert.True(t, len(parsed.RefreshToken) > 10) +} + +func TestAccessTokenExchangeWithoutPKCE(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequestWithJSON(t, "POST", "/login/oauth/access_token", map[string]string{ + "grant_type": "authorization_code", + "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138", + "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=", + "redirect_uri": "a", + "code": "authcode", + "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally + }) + resp := MakeRequest(t, req, http.StatusOK) + type response struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int64 `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + } + parsed := new(response) + + assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed)) + assert.True(t, len(parsed.AccessToken) > 10) + assert.True(t, len(parsed.RefreshToken) > 10) +} + +func TestAccessTokenExchangeJSON(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequestWithJSON(t, "POST", "/login/oauth/access_token", map[string]string{ + "grant_type": "authorization_code", + "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138", + "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=", + "redirect_uri": "a", + "code": "authcode", + }) + MakeRequest(t, req, http.StatusBadRequest) +} + +func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) { + defer tests.PrepareTestEnv(t)() + // invalid client id + req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ + "grant_type": "authorization_code", + "client_id": "???", + "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=", + "redirect_uri": "a", + "code": "authcode", + "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally + }) + MakeRequest(t, req, http.StatusBadRequest) + // invalid client secret + req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ + "grant_type": "authorization_code", + "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138", + "client_secret": "???", + "redirect_uri": "a", + "code": "authcode", + "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally + }) + MakeRequest(t, req, http.StatusBadRequest) + // invalid redirect uri + req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ + "grant_type": "authorization_code", + "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138", + "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=", + "redirect_uri": "???", + "code": "authcode", + "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally + }) + MakeRequest(t, req, http.StatusBadRequest) + // invalid authorization code + req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ + "grant_type": "authorization_code", + "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138", + "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=", + "redirect_uri": "a", + "code": "???", + "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally + }) + MakeRequest(t, req, http.StatusBadRequest) + // invalid grant_type + req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ + "grant_type": "???", + "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138", + "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=", + "redirect_uri": "a", + "code": "authcode", + "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally + }) + MakeRequest(t, req, http.StatusBadRequest) +} + +func TestAccessTokenExchangeWithBasicAuth(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ + "grant_type": "authorization_code", + "redirect_uri": "a", + "code": "authcode", + "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally + }) + req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9") + resp := MakeRequest(t, req, http.StatusOK) + type response struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int64 `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + } + parsed := new(response) + + assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed)) + assert.True(t, len(parsed.AccessToken) > 10) + assert.True(t, len(parsed.RefreshToken) > 10) + + // use wrong client_secret + req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ + "grant_type": "authorization_code", + "redirect_uri": "a", + "code": "authcode", + "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally + }) + req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OmJsYWJsYQ==") + MakeRequest(t, req, http.StatusBadRequest) + + // missing header + req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ + "grant_type": "authorization_code", + "redirect_uri": "a", + "code": "authcode", + "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally + }) + MakeRequest(t, req, http.StatusBadRequest) +} + +func TestRefreshTokenInvalidation(t *testing.T) { + defer tests.PrepareTestEnv(t)() + req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ + "grant_type": "authorization_code", + "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138", + "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=", + "redirect_uri": "a", + "code": "authcode", + "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally + }) + resp := MakeRequest(t, req, http.StatusOK) + type response struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int64 `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + } + parsed := new(response) + + assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed)) + + // test without invalidation + setting.OAuth2.InvalidateRefreshTokens = false + + refreshReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ + "grant_type": "refresh_token", + "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138", + "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=", + "redirect_uri": "a", + "refresh_token": parsed.RefreshToken, + }) + + bs, err := io.ReadAll(refreshReq.Body) + assert.NoError(t, err) + + refreshReq.Body = io.NopCloser(bytes.NewReader(bs)) + MakeRequest(t, refreshReq, http.StatusOK) + + refreshReq.Body = io.NopCloser(bytes.NewReader(bs)) + MakeRequest(t, refreshReq, http.StatusOK) + + // test with invalidation + setting.OAuth2.InvalidateRefreshTokens = true + refreshReq.Body = io.NopCloser(bytes.NewReader(bs)) + MakeRequest(t, refreshReq, http.StatusOK) + + refreshReq.Body = io.NopCloser(bytes.NewReader(bs)) + MakeRequest(t, refreshReq, http.StatusBadRequest) +} diff --git a/tests/integration/org_count_test.go b/tests/integration/org_count_test.go new file mode 100644 index 0000000000..96f39924f1 --- /dev/null +++ b/tests/integration/org_count_test.go @@ -0,0 +1,147 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/url" + "strings" + "testing" + + "code.gitea.io/gitea/models/organization" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func TestOrgCounts(t *testing.T) { + onGiteaRun(t, testOrgCounts) +} + +func testOrgCounts(t *testing.T, u *url.URL) { + orgOwner := "user2" + orgName := "testOrg" + orgCollaborator := "user4" + ctx := NewAPITestContext(t, orgOwner, "repo1") + + var ownerCountRepos map[string]int + var collabCountRepos map[string]int + + t.Run("GetTheOwnersNumRepos", doCheckOrgCounts(orgOwner, map[string]int{}, + false, + func(_ *testing.T, calcOrgCounts map[string]int) { + ownerCountRepos = calcOrgCounts + }, + )) + t.Run("GetTheCollaboratorsNumRepos", doCheckOrgCounts(orgCollaborator, map[string]int{}, + false, + func(_ *testing.T, calcOrgCounts map[string]int) { + collabCountRepos = calcOrgCounts + }, + )) + + t.Run("CreatePublicTestOrganization", doAPICreateOrganization(ctx, &api.CreateOrgOption{ + UserName: orgName, + Visibility: "public", + })) + + // Following the creation of the organization, the orgName must appear in the counts with 0 repos + ownerCountRepos[orgName] = 0 + + t.Run("AssertNumRepos0ForTestOrg", doCheckOrgCounts(orgOwner, ownerCountRepos, true)) + + // the collaborator is not a collaborator yet + t.Run("AssertNoTestOrgReposForCollaborator", doCheckOrgCounts(orgCollaborator, collabCountRepos, true)) + + t.Run("CreateOrganizationPrivateRepo", doAPICreateOrganizationRepository(ctx, orgName, &api.CreateRepoOption{ + Name: "privateTestRepo", + AutoInit: true, + Private: true, + })) + + ownerCountRepos[orgName] = 1 + t.Run("AssertNumRepos1ForTestOrg", doCheckOrgCounts(orgOwner, ownerCountRepos, true)) + + t.Run("AssertNoTestOrgReposForCollaborator", doCheckOrgCounts(orgCollaborator, collabCountRepos, true)) + + var testTeam api.Team + + t.Run("CreateTeamForPublicTestOrganization", doAPICreateOrganizationTeam(ctx, orgName, &api.CreateTeamOption{ + Name: "test", + Permission: "read", + Units: []string{"repo.code", "repo.issues", "repo.wiki", "repo.pulls", "repo.releases"}, + CanCreateOrgRepo: true, + }, func(_ *testing.T, team api.Team) { + testTeam = team + })) + + t.Run("AssertNoTestOrgReposForCollaborator", doCheckOrgCounts(orgCollaborator, collabCountRepos, true)) + + t.Run("AddCollboratorToTeam", doAPIAddUserToOrganizationTeam(ctx, testTeam.ID, orgCollaborator)) + + collabCountRepos[orgName] = 0 + t.Run("AssertNumRepos0ForTestOrgForCollaborator", doCheckOrgCounts(orgOwner, ownerCountRepos, true)) + + // Now create a Public Repo + t.Run("CreateOrganizationPublicRepo", doAPICreateOrganizationRepository(ctx, orgName, &api.CreateRepoOption{ + Name: "publicTestRepo", + AutoInit: true, + })) + + ownerCountRepos[orgName] = 2 + t.Run("AssertNumRepos2ForTestOrg", doCheckOrgCounts(orgOwner, ownerCountRepos, true)) + collabCountRepos[orgName] = 1 + t.Run("AssertNumRepos1ForTestOrgForCollaborator", doCheckOrgCounts(orgOwner, ownerCountRepos, true)) + + // Now add the testTeam to the privateRepo + t.Run("AddTestTeamToPrivateRepo", doAPIAddRepoToOrganizationTeam(ctx, testTeam.ID, orgName, "privateTestRepo")) + + t.Run("AssertNumRepos2ForTestOrg", doCheckOrgCounts(orgOwner, ownerCountRepos, true)) + collabCountRepos[orgName] = 2 + t.Run("AssertNumRepos2ForTestOrgForCollaborator", doCheckOrgCounts(orgOwner, ownerCountRepos, true)) +} + +func doCheckOrgCounts(username string, orgCounts map[string]int, strict bool, callback ...func(*testing.T, map[string]int)) func(t *testing.T) { + canonicalCounts := make(map[string]int, len(orgCounts)) + + for key, value := range orgCounts { + newKey := strings.TrimSpace(strings.ToLower(key)) + canonicalCounts[newKey] = value + } + + return func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: username, + }) + + orgs, err := organization.FindOrgs(organization.FindOrgOptions{ + UserID: user.ID, + IncludePrivate: true, + }) + assert.NoError(t, err) + + calcOrgCounts := map[string]int{} + + for _, org := range orgs { + calcOrgCounts[org.LowerName] = org.NumRepos + count, ok := canonicalCounts[org.LowerName] + if ok { + assert.True(t, count == org.NumRepos, "Number of Repos in %s is %d when we expected %d", org.Name, org.NumRepos, count) + } else { + assert.False(t, strict, "Did not expect to see %s with count %d", org.Name, org.NumRepos) + } + } + + for key, value := range orgCounts { + _, seen := calcOrgCounts[strings.TrimSpace(strings.ToLower(key))] + assert.True(t, seen, "Expected to see %s with %d but did not", key, value) + } + + if len(callback) > 0 { + callback[0](t, calcOrgCounts) + } + } +} diff --git a/tests/integration/org_test.go b/tests/integration/org_test.go new file mode 100644 index 0000000000..d04fcf7f57 --- /dev/null +++ b/tests/integration/org_test.go @@ -0,0 +1,224 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestOrgRepos(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + var ( + users = []string{"user1", "user2"} + cases = map[string][]string{ + "alphabetically": {"repo21", "repo3", "repo5"}, + "reversealphabetically": {"repo5", "repo3", "repo21"}, + } + ) + + for _, user := range users { + t.Run(user, func(t *testing.T) { + session := loginUser(t, user) + for sortBy, repos := range cases { + req := NewRequest(t, "GET", "/user3?sort="+sortBy) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + sel := htmlDoc.doc.Find("a.name") + assert.Len(t, repos, len(sel.Nodes)) + for i := 0; i < len(repos); i++ { + assert.EqualValues(t, repos[i], strings.TrimSpace(sel.Eq(i).Text())) + } + } + }) + } +} + +func TestLimitedOrg(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // not logged in user + req := NewRequest(t, "GET", "/limited_org") + MakeRequest(t, req, http.StatusNotFound) + req = NewRequest(t, "GET", "/limited_org/public_repo_on_limited_org") + MakeRequest(t, req, http.StatusNotFound) + req = NewRequest(t, "GET", "/limited_org/private_repo_on_limited_org") + MakeRequest(t, req, http.StatusNotFound) + + // login non-org member user + session := loginUser(t, "user2") + req = NewRequest(t, "GET", "/limited_org") + session.MakeRequest(t, req, http.StatusOK) + req = NewRequest(t, "GET", "/limited_org/public_repo_on_limited_org") + session.MakeRequest(t, req, http.StatusOK) + req = NewRequest(t, "GET", "/limited_org/private_repo_on_limited_org") + session.MakeRequest(t, req, http.StatusNotFound) + + // site admin + session = loginUser(t, "user1") + req = NewRequest(t, "GET", "/limited_org") + session.MakeRequest(t, req, http.StatusOK) + req = NewRequest(t, "GET", "/limited_org/public_repo_on_limited_org") + session.MakeRequest(t, req, http.StatusOK) + req = NewRequest(t, "GET", "/limited_org/private_repo_on_limited_org") + session.MakeRequest(t, req, http.StatusOK) +} + +func TestPrivateOrg(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // not logged in user + req := NewRequest(t, "GET", "/privated_org") + MakeRequest(t, req, http.StatusNotFound) + req = NewRequest(t, "GET", "/privated_org/public_repo_on_private_org") + MakeRequest(t, req, http.StatusNotFound) + req = NewRequest(t, "GET", "/privated_org/private_repo_on_private_org") + MakeRequest(t, req, http.StatusNotFound) + + // login non-org member user + session := loginUser(t, "user2") + req = NewRequest(t, "GET", "/privated_org") + session.MakeRequest(t, req, http.StatusNotFound) + req = NewRequest(t, "GET", "/privated_org/public_repo_on_private_org") + session.MakeRequest(t, req, http.StatusNotFound) + req = NewRequest(t, "GET", "/privated_org/private_repo_on_private_org") + session.MakeRequest(t, req, http.StatusNotFound) + + // non-org member who is collaborator on repo in private org + session = loginUser(t, "user4") + req = NewRequest(t, "GET", "/privated_org") + session.MakeRequest(t, req, http.StatusNotFound) + req = NewRequest(t, "GET", "/privated_org/public_repo_on_private_org") // colab of this repo + session.MakeRequest(t, req, http.StatusOK) + req = NewRequest(t, "GET", "/privated_org/private_repo_on_private_org") + session.MakeRequest(t, req, http.StatusNotFound) + + // site admin + session = loginUser(t, "user1") + req = NewRequest(t, "GET", "/privated_org") + session.MakeRequest(t, req, http.StatusOK) + req = NewRequest(t, "GET", "/privated_org/public_repo_on_private_org") + session.MakeRequest(t, req, http.StatusOK) + req = NewRequest(t, "GET", "/privated_org/private_repo_on_private_org") + session.MakeRequest(t, req, http.StatusOK) +} + +func TestOrgMembers(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // not logged in user + req := NewRequest(t, "GET", "/org/org25/members") + MakeRequest(t, req, http.StatusOK) + + // org member + session := loginUser(t, "user24") + req = NewRequest(t, "GET", "/org/org25/members") + session.MakeRequest(t, req, http.StatusOK) + + // site admin + session = loginUser(t, "user1") + req = NewRequest(t, "GET", "/org/org25/members") + session.MakeRequest(t, req, http.StatusOK) +} + +func TestOrgRestrictedUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // privated_org is a private org who has id 23 + orgName := "privated_org" + + // public_repo_on_private_org is a public repo on privated_org + repoName := "public_repo_on_private_org" + + // user29 is a restricted user who is not a member of the organization + restrictedUser := "user29" + + // #17003 reports a bug whereby adding a restricted user to a read-only team doesn't work + + // assert restrictedUser cannot see the org or the public repo + restrictedSession := loginUser(t, restrictedUser) + req := NewRequest(t, "GET", fmt.Sprintf("/%s", orgName)) + restrictedSession.MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName)) + restrictedSession.MakeRequest(t, req, http.StatusNotFound) + + // Therefore create a read-only team + adminSession := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, adminSession) + + teamToCreate := &api.CreateTeamOption{ + Name: "codereader", + Description: "Code Reader", + IncludesAllRepositories: true, + Permission: "read", + Units: []string{"repo.code"}, + } + + req = NewRequestWithJSON(t, "POST", + fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", orgName, token), teamToCreate) + + var apiTeam api.Team + + resp := adminSession.MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, &apiTeam) + checkTeamResponse(t, "CreateTeam_codereader", &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, + teamToCreate.Permission, teamToCreate.Units, nil) + checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, + teamToCreate.Permission, teamToCreate.Units, nil) + // teamID := apiTeam.ID + + // Now we need to add the restricted user to the team + req = NewRequest(t, "PUT", + fmt.Sprintf("/api/v1/teams/%d/members/%s?token=%s", apiTeam.ID, restrictedUser, token)) + _ = adminSession.MakeRequest(t, req, http.StatusNoContent) + + // Now we need to check if the restrictedUser can access the repo + req = NewRequest(t, "GET", fmt.Sprintf("/%s", orgName)) + restrictedSession.MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName)) + restrictedSession.MakeRequest(t, req, http.StatusOK) +} + +func TestTeamSearch(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) + + var results TeamSearchResults + + session := loginUser(t, user.Name) + csrf := GetCSRF(t, session, "/"+org.Name) + req := NewRequestf(t, "GET", "/org/%s/teams/-/search?q=%s", org.Name, "_team") + req.Header.Add("X-Csrf-Token", csrf) + resp := session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &results) + assert.NotEmpty(t, results.Data) + assert.Len(t, results.Data, 2) + assert.Equal(t, "review_team", results.Data[0].Name) + assert.Equal(t, "test_team", results.Data[1].Name) + + // no access if not organization member + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + session = loginUser(t, user5.Name) + csrf = GetCSRF(t, session, "/"+org.Name) + req = NewRequestf(t, "GET", "/org/%s/teams/-/search?q=%s", org.Name, "team") + req.Header.Add("X-Csrf-Token", csrf) + session.MakeRequest(t, req, http.StatusNotFound) +} diff --git a/tests/integration/private-testing.key b/tests/integration/private-testing.key new file mode 100644 index 0000000000..b3874eab80 --- /dev/null +++ b/tests/integration/private-testing.key @@ -0,0 +1,81 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQVYBGG44vABDAC7VVdrVcU2CzI4P1vm0HtsgRCj9TsCpxjESleIheG/jrLjpVaF +YrlVKQ0+q6HXOMcbjJnsm+N6hgZNqwaKTNC6+LJZMXHlPG8wUGrHgHyUZ03urYB6 +vjlJ70RUBu1+dB5yJcTOk7kMLx8/is9FlAEEY/G98aviv2m3My6B5SJ2BErjREIw +eRnWFm+JDcga9nRi8ra/DMac45iQ4IcQcj0NDlCn3aY88nGa6o1+07h7wYwI3t8S ++pfITuJgWf2cYK49v9QVsBMR8XHuS8UDGFuJ1Y4KK5zMHWKhah/6isyWPSgiC0wo +V7LZDJp/tN8IoQf2fchRQN+x0PBeVXdt3KGXqvsfk7hnwGDKjGMp4nTxL8PFhpG8 +KJP0tTA063bbnrGjVYHaulTBTSKS8R3Zk2utA8JUgTU6tkNFoh8rNLgh2xtw/Ci3 +kvKzTdikWxBfspYgrWloMyCTZwOHssARyarXgtysEI1hNpvgpJo0WZOMurYuFDIB +kEqgnqe1b1B7ItcAEQEAAQAL/iNebgZkZ7sX6w/mmn3eL+dhCNjD5LPQA6OP2635 +hRFLKmhDn63IYXB8MzV5ZzGA1UrUxX0AQ7cu1cLVPwNelGwwp0+iv7vFqMKI9Fgd +YKgORw8AsAi8oIlehNqOgkmFN/haPCm6h04PGYnANfkPhA+lpQ81MTw64oVFwwqg +TdzVW6RED3EidCfRDZblRLoefQPvimRQz7DwYa48zhNjVjaAVOcUuJ26MovKrBNd +eu/Wr48/MQPez0hw6FnDs9fSAtB/cLmSlSL3yBkDB4RHTne6amvemX5SyQqOSKLJ +F+YM33yIN3NQNQtJUkjNkBWuIe+s8pxFuKTHNyulCe/ES0ivtnqaCJ/J/PPzn/3t +2S5f1K26jqJEnu4SfCxG3xTbSMu9DIcDP6BkU6WK9dQCPyfWZ3r3QkgZjHt02HP9 +Gbzh2tSxBO3b4ujysdSB2l78I0s3XLWae6FPNNKG+zmlCV8mUEa+OFVjS60GrX83 +NQVfoyjNdSQkLlg3+bo5DFma+QYAwr/HXi06iC8dh23HkPkYedIOml70SPAQqvVj +xYtZRRSXo98P+QtA2kX0G3/9f606n2qqA9JXc3m4euvE94oSp708M5xAkSfdsc6B +QIDNrR5ty+f+WdhZAsW4Gu/XbQ5ndkRReTtc3UtzIrC0zg8egCoE0yMfCJWPS2nF +QTdlsl+cXDSQj7UMfCP9cKSsTzdEAF/P5ALI7Y+W4va/gy/0czJne+ZNMxPWE+Gs +00KJCbSfgktnYhVt/XdWKuRZ8ylZBgD2QHts6MHkfno/OUK3wYDB7zLMIBdLltlg +wvp7CXh8hIxzNqxaAjGus1XAg+/7QbSey/t88CR9XQsekd/L8NIYaFOxSpVAe03V +RaW2/EXtmKIHKoWBTQJLJle3mp+iUiVjzdmTyUAqhFaCBYVMBlSvBuC99jXnu3U3 +UcUelLDvP2ufMdeXhVU1Anfg45wqvyfPIAhpgYMmyprGpfkd2Sf2W1ThaTec0kI1 +cT7AtkrqijCGDgo9ohl8ojmRhRCl968F/imENQATANdkhbYJ0k1+Ubm690xYNN7u +d+wnQzS9P/UPpMrC4H2esz9g+Nls7X6/jeGB6K0bpOYAUR1VlRfuXREJcy9bK9Q8 +gzfBC4XWELA726fc9YeJqWH4fI9SFx0AjVVx6VFwSiDcoYbX26CLZN+jY6Gx8kx6 +PrOf4tPCU+8EP5f/tYn/dwN9oQPoyM7bYyN/zcrupLhHON7ryFr++Kpiw0feBGbg +kEP+0HWJ2cX1MvcqTurx344RVlmnEBesDuFstBhnaXRlYSA8Z2l0ZWFAZmFrZS5s +b2NhbD6JAc4EEwEKADgWIQT3rIVBIbYw8mUW1p+Z3Yqpy9FcAQUCYbji8AIbAwUL +CQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCZ3Yqpy9FcAY6AC/9GUc0vGAmZ1N7P +ThOxy3SvoIWJzycEu6DKdp4FlucKW9Rm66vCwPDg7XcQxZQTIWNPIGB3kln0yRdx +zRtGLKIDPo2qW8kPrLN3GXToKX2mBb76duaShW34W1rUVY613olmtwLT+QqgRX+H +x0rNNJloOh3kawwaMoYZy4B2vq7AZ5ybIsT4ROKgKPzAlajI4+jI+qKA5GSyP7Jq +Tu254BCeg0v51p0VWIbGdgPyVkZkLtrlxN7s8UGDoTUAJgB/K3SOGNtQFSxnJba5 +q0YBxDUScd65b1+YCUHY+3FdC4/5168y4Zic9bBxeVu3jBwSVDvrELsWzIDNgHmP +eyl/Nv+CTZDDKOtzpS823k7gC129rcxMk0mkIzAt/wG7N4zf0vpt02LZ/Ei/azqK +xq782Fmc3un+pgQWJrlU2ZT7yHi6aJAfxfDpQZwz8qXGgdaFsumNylEWy9o80pJG +8RYgM+phZL4INYIiHoWUuz2v+qK9jmhxtTLOpKDXxtGrz6aFWJadBVgEYbji8AEM +AMDFivCjl7vGACeST4iboZw817uAJFOTOk3uOnXuAx5NLq/DbL3Cyhjictwxhxot +U1MdAZOSOHlWPBJTiib1145rDTJCH6gwQNVaqn/V0i/Dc2Isua4YF0efztzwD2aH +NX4RCDp74bQ08YTsAlCWHk7blg3NCU/y4maaxdJ26PsNrIiY0l5SC3oNiEAp2aWP +Yf+plmQwqk+Z3laB5fkVz8Vca8TZle11/NZVVwrpq8rubPUYHC2KmabFLihcMCGv +eTt3LCB7tDzohDmX/0vuqTD09YTv5gmIzU/tx4+qH4tVfCK/DKTxsxafY4KZY4kM +hqrhuGWq8EAu4RUG6AzbSDJZnO1UAfzC9j/8upr3qxOXx/xhWKzixGrRXo9eK5eR +1pqEj+XGH+f9bQiF/pEIojcUp45S0ZBaSBPj2W7TZbbHzqXYNzmXa3IVdz+9l7MB +cRfIe67wt4h66/fmATe47KvHNRfKpyhFD2utdOSd61tKXo/bu/5LBath0mxMBPHd +4QARAQABAAv5Aacf824U/LiW+JU4poVJFofEr22gQhwwIt9rnmZm80ak+L+o9MaR +CN4WLzJN2X5b1B8FTAXerexR8bPy1QsvaN/yRMT23wW3j0IVVf5tbIM/6m6o5+fP +zp7S5/zh8OvbXE7v6Qp2C19sgQqB/ugOmff9hSBF18A6II2Wq8uLtgKua5xof1kI +5/1qNpH1SltcndPPKjbq8D7zk6kjoZCw5PJk1ShVcKwIjzDmS729qezZ6nm6sh7v +BX70JUdHErQzBtcb+Y39nRC/7aQ/X5s73Iy9OsnAzzTSTtw1RgxgAYXxQKhQN5xP +rzUdZqCSFicjLAPvY4PxQmIL+DS7tb/rrWUJAfr/9LcrzoOC5LaYFTuykq231ORs +4oRfHmJqYAiMYQ7iXMtFVspxQWq/8qrBPmmEkS2oAnmd8Ld5hbd7sFBsS5GCW9a3 +UyQQ9WQECyvpgFOR9m746/bFjKMgG+aBHyKvndniF3XWjHWrzrbk5vAViMb+9Al+ +7MxSqZ/oNrvdBgDT6hTMwyNBvQwJ/Lev0S3XPDJmxg+Y8QIrNbBrXjA70yVeLFgr +emDnfdAwuhmZ5vKRe2YcIyMIOagRIDUEWs8EyCvM2e+bF+I0meQvWT536Cm2TouI +jCUIip4HRTwe7NAR50OMACtji8sbcmfnIfFMfGUS3dPpNGURhCEHxWB6hlvbkbkV +CToTlMS/agY0sV4O4kWqWiaKgZRefJSiVfj6RDKs43SbNxhJu+DslU7PPlfv6SFJ +nX9LWE6daLrpuF0GAOjf+kjqpFFgF50h3B7lCsSfxIKW587z93rkmccGKvZj4Qeq +ahjekO6kxapYJhtjY9BOQdU0rzEPhh8bF39GE/iCfXVdIh1suqp3uQv9birgkWJN +CROrHvk5NmlBBb4BDid0hY8hM3lEi+6rK2lhs4krpoHin/h852AI+YBzeAVYSqor +fqEzCiPlX7f1EI3I6kPnGrgeIWcznOO0yXkM/QuKCDWZlaLDxu7Rc5lBnsmiChrT +3HwOiyOFfU1Rib/TVQYAng1PxHZfIfC77cblAiv3SXjFtSDIfyueER3Ii11DyEfB +zco+qbpqYiDEI7yLZFuyExEpT2GbHTTEn28aEZzZBv/aFRnVFPTMiyquFE7QKuLc +aEpEYZE3qSiAUDAckfDblM1SHZAVP6CaStkoUigtYBND2F316MTNGGLtcJ4y9s1r +soqvCJ/cx0lR359kljqCHyv+iMqeBttwTGjFbiNJ5as4ATA988FlR6PnB0cr+Lg2 +8X3xiRcAaxlLFcUOifpa3m6JAbYEGAEKACAWIQT3rIVBIbYw8mUW1p+Z3Yqpy9Fc +AQUCYbji8AIbDAAKCRCZ3Yqpy9FcAT/pDACilZ8zPUs+MwwI0BI6dMWxmhusHwTx +kdwbxt2TuCQE3DEftCTCaxO5f8hQ6CL9pxYw5mn/6p8ELUpindFxgzpBjUQZyynb ++ZA7LOK5gKw25vGTRcMFiWZOBnMEAifyywmG6XCPtio8i3/In95ix/Adi17tzdpy +EfFfWTeDocTNPhIPhg9REteZ71eBW3qEbY2iCeG3XSpKhkj6obY7BL8xLT9iaezh +C6Upzb3gvjEInoaMR2yra9fVugW32lCFgXr6UZ5osBqVNjXGcwBqxg5IAkt4R5v1 +vdt5h69cagkbdS0qSRbS56GctmxVnbWyuAuKON55BDri5BhO3V4GmIXXUW12dQhl +1/P9+xMjHm424QlGL7jgEzOMR5CJFdDQ+osabA2iZAUEQ7Ut8SgREfCduqKqzJ7z +Uvb3feuoW45VNBqv7op8hH1S8okFaCTuznrAPqGXxee0I3oTX1lbBW+IySoisWMC +ZMtt+nu5oJo/m1bvhWiYLhW6WX8TcmRKD3s= +=V9rS +-----END PGP PRIVATE KEY BLOCK----- diff --git a/tests/integration/privateactivity_test.go b/tests/integration/privateactivity_test.go new file mode 100644 index 0000000000..3f352e49c6 --- /dev/null +++ b/tests/integration/privateactivity_test.go @@ -0,0 +1,417 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + activities_model "code.gitea.io/gitea/models/activities" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +const ( + privateActivityTestAdmin = "user1" + privateActivityTestUser = "user2" +) + +// user3 is an organization so it is not usable here +const privateActivityTestOtherUser = "user4" + +// activity helpers + +func testPrivateActivityDoSomethingForActionEntries(t *testing.T) { + repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID}) + + session := loginUser(t, privateActivityTestUser) + token := getTokenForLoggedInUser(t, session) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues?state=all&token=%s", owner.Name, repoBefore.Name, token) + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{ + Body: "test", + Title: "test", + }) + session.MakeRequest(t, req, http.StatusCreated) +} + +// private activity helpers + +func testPrivateActivityHelperEnablePrivateActivity(t *testing.T) { + session := loginUser(t, privateActivityTestUser) + req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ + "_csrf": GetCSRF(t, session, "/user/settings"), + "name": privateActivityTestUser, + "email": privateActivityTestUser + "@example.com", + "language": "en-US", + "keep_activity_private": "1", + }) + session.MakeRequest(t, req, http.StatusSeeOther) +} + +func testPrivateActivityHelperHasVisibleActivitiesInHTMLDoc(htmlDoc *HTMLDoc) bool { + return htmlDoc.doc.Find(".feeds").Find(".news").Length() > 0 +} + +func testPrivateActivityHelperHasVisibleActivitiesFromSession(t *testing.T, session *TestSession) bool { + req := NewRequestf(t, "GET", "/%s?tab=activity", privateActivityTestUser) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + return testPrivateActivityHelperHasVisibleActivitiesInHTMLDoc(htmlDoc) +} + +func testPrivateActivityHelperHasVisibleActivitiesFromPublic(t *testing.T) bool { + req := NewRequestf(t, "GET", "/%s?tab=activity", privateActivityTestUser) + resp := MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + return testPrivateActivityHelperHasVisibleActivitiesInHTMLDoc(htmlDoc) +} + +// heatmap UI helpers + +func testPrivateActivityHelperHasVisibleHeatmapInHTMLDoc(htmlDoc *HTMLDoc) bool { + return htmlDoc.doc.Find("#user-heatmap").Length() > 0 +} + +func testPrivateActivityHelperHasVisibleProfileHeatmapFromSession(t *testing.T, session *TestSession) bool { + req := NewRequestf(t, "GET", "/%s?tab=activity", privateActivityTestUser) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + return testPrivateActivityHelperHasVisibleHeatmapInHTMLDoc(htmlDoc) +} + +func testPrivateActivityHelperHasVisibleDashboardHeatmapFromSession(t *testing.T, session *TestSession) bool { + req := NewRequest(t, "GET", "/") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + return testPrivateActivityHelperHasVisibleHeatmapInHTMLDoc(htmlDoc) +} + +func testPrivateActivityHelperHasVisibleHeatmapFromPublic(t *testing.T) bool { + req := NewRequestf(t, "GET", "/%s?tab=activity", privateActivityTestUser) + resp := MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + return testPrivateActivityHelperHasVisibleHeatmapInHTMLDoc(htmlDoc) +} + +// heatmap API helpers + +func testPrivateActivityHelperHasHeatmapContentFromPublic(t *testing.T) bool { + req := NewRequestf(t, "GET", "/api/v1/users/%s/heatmap", privateActivityTestUser) + resp := MakeRequest(t, req, http.StatusOK) + + var items []*activities_model.UserHeatmapData + DecodeJSON(t, resp, &items) + + return len(items) != 0 +} + +func testPrivateActivityHelperHasHeatmapContentFromSession(t *testing.T, session *TestSession) bool { + token := getTokenForLoggedInUser(t, session) + + req := NewRequestf(t, "GET", "/api/v1/users/%s/heatmap?token=%s", privateActivityTestUser, token) + resp := session.MakeRequest(t, req, http.StatusOK) + + var items []*activities_model.UserHeatmapData + DecodeJSON(t, resp, &items) + + return len(items) != 0 +} + +// check activity visibility if the visibility is enabled + +func TestPrivateActivityNoVisibleForPublic(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + + visible := testPrivateActivityHelperHasVisibleActivitiesFromPublic(t) + + assert.True(t, visible, "user should have visible activities") +} + +func TestPrivateActivityNoVisibleForUserItself(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + + session := loginUser(t, privateActivityTestUser) + visible := testPrivateActivityHelperHasVisibleActivitiesFromSession(t, session) + + assert.True(t, visible, "user should have visible activities") +} + +func TestPrivateActivityNoVisibleForOtherUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + + session := loginUser(t, privateActivityTestOtherUser) + visible := testPrivateActivityHelperHasVisibleActivitiesFromSession(t, session) + + assert.True(t, visible, "user should have visible activities") +} + +func TestPrivateActivityNoVisibleForAdmin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + + session := loginUser(t, privateActivityTestAdmin) + visible := testPrivateActivityHelperHasVisibleActivitiesFromSession(t, session) + + assert.True(t, visible, "user should have visible activities") +} + +// check activity visibility if the visibility is disabled + +func TestPrivateActivityYesInvisibleForPublic(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + testPrivateActivityHelperEnablePrivateActivity(t) + + visible := testPrivateActivityHelperHasVisibleActivitiesFromPublic(t) + + assert.False(t, visible, "user should have no visible activities") +} + +func TestPrivateActivityYesVisibleForUserItself(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + testPrivateActivityHelperEnablePrivateActivity(t) + + session := loginUser(t, privateActivityTestUser) + visible := testPrivateActivityHelperHasVisibleActivitiesFromSession(t, session) + + assert.True(t, visible, "user should have visible activities") +} + +func TestPrivateActivityYesInvisibleForOtherUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + testPrivateActivityHelperEnablePrivateActivity(t) + + session := loginUser(t, privateActivityTestOtherUser) + visible := testPrivateActivityHelperHasVisibleActivitiesFromSession(t, session) + + assert.False(t, visible, "user should have no visible activities") +} + +func TestPrivateActivityYesVisibleForAdmin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + testPrivateActivityHelperEnablePrivateActivity(t) + + session := loginUser(t, privateActivityTestAdmin) + visible := testPrivateActivityHelperHasVisibleActivitiesFromSession(t, session) + + assert.True(t, visible, "user should have visible activities") +} + +// check heatmap visibility if the visibility is enabled + +func TestPrivateActivityNoHeatmapVisibleForPublic(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + + visible := testPrivateActivityHelperHasVisibleHeatmapFromPublic(t) + + assert.True(t, visible, "user should have visible heatmap") +} + +func TestPrivateActivityNoHeatmapVisibleForUserItselfAtProfile(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + + session := loginUser(t, privateActivityTestUser) + visible := testPrivateActivityHelperHasVisibleProfileHeatmapFromSession(t, session) + + assert.True(t, visible, "user should have visible heatmap") +} + +func TestPrivateActivityNoHeatmapVisibleForUserItselfAtDashboard(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + + session := loginUser(t, privateActivityTestUser) + visible := testPrivateActivityHelperHasVisibleDashboardHeatmapFromSession(t, session) + + assert.True(t, visible, "user should have visible heatmap") +} + +func TestPrivateActivityNoHeatmapVisibleForOtherUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + + session := loginUser(t, privateActivityTestOtherUser) + visible := testPrivateActivityHelperHasVisibleProfileHeatmapFromSession(t, session) + + assert.True(t, visible, "user should have visible heatmap") +} + +func TestPrivateActivityNoHeatmapVisibleForAdmin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + + session := loginUser(t, privateActivityTestAdmin) + visible := testPrivateActivityHelperHasVisibleProfileHeatmapFromSession(t, session) + + assert.True(t, visible, "user should have visible heatmap") +} + +// check heatmap visibility if the visibility is disabled + +func TestPrivateActivityYesHeatmapInvisibleForPublic(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + testPrivateActivityHelperEnablePrivateActivity(t) + + visible := testPrivateActivityHelperHasVisibleHeatmapFromPublic(t) + + assert.False(t, visible, "user should have no visible heatmap") +} + +func TestPrivateActivityYesHeatmapVisibleForUserItselfAtProfile(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + testPrivateActivityHelperEnablePrivateActivity(t) + + session := loginUser(t, privateActivityTestUser) + visible := testPrivateActivityHelperHasVisibleProfileHeatmapFromSession(t, session) + + assert.True(t, visible, "user should have visible heatmap") +} + +func TestPrivateActivityYesHeatmapVisibleForUserItselfAtDashboard(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + testPrivateActivityHelperEnablePrivateActivity(t) + + session := loginUser(t, privateActivityTestUser) + visible := testPrivateActivityHelperHasVisibleDashboardHeatmapFromSession(t, session) + + assert.True(t, visible, "user should have visible heatmap") +} + +func TestPrivateActivityYesHeatmapInvisibleForOtherUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + testPrivateActivityHelperEnablePrivateActivity(t) + + session := loginUser(t, privateActivityTestOtherUser) + visible := testPrivateActivityHelperHasVisibleProfileHeatmapFromSession(t, session) + + assert.False(t, visible, "user should have no visible heatmap") +} + +func TestPrivateActivityYesHeatmapVisibleForAdmin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + testPrivateActivityHelperEnablePrivateActivity(t) + + session := loginUser(t, privateActivityTestAdmin) + visible := testPrivateActivityHelperHasVisibleProfileHeatmapFromSession(t, session) + + assert.True(t, visible, "user should have visible heatmap") +} + +// check heatmap api provides content if the visibility is enabled + +func TestPrivateActivityNoHeatmapHasContentForPublic(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + + hasContent := testPrivateActivityHelperHasHeatmapContentFromPublic(t) + + assert.True(t, hasContent, "user should have heatmap content") +} + +func TestPrivateActivityNoHeatmapHasContentForUserItself(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + + session := loginUser(t, privateActivityTestUser) + hasContent := testPrivateActivityHelperHasHeatmapContentFromSession(t, session) + + assert.True(t, hasContent, "user should have heatmap content") +} + +func TestPrivateActivityNoHeatmapHasContentForOtherUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + + session := loginUser(t, privateActivityTestOtherUser) + hasContent := testPrivateActivityHelperHasHeatmapContentFromSession(t, session) + + assert.True(t, hasContent, "user should have heatmap content") +} + +func TestPrivateActivityNoHeatmapHasContentForAdmin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + + session := loginUser(t, privateActivityTestAdmin) + hasContent := testPrivateActivityHelperHasHeatmapContentFromSession(t, session) + + assert.True(t, hasContent, "user should have heatmap content") +} + +// check heatmap api provides no content if the visibility is disabled +// this should be equal to the hidden heatmap at the UI + +func TestPrivateActivityYesHeatmapHasNoContentForPublic(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + testPrivateActivityHelperEnablePrivateActivity(t) + + hasContent := testPrivateActivityHelperHasHeatmapContentFromPublic(t) + + assert.False(t, hasContent, "user should have no heatmap content") +} + +func TestPrivateActivityYesHeatmapHasNoContentForUserItself(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + testPrivateActivityHelperEnablePrivateActivity(t) + + session := loginUser(t, privateActivityTestUser) + hasContent := testPrivateActivityHelperHasHeatmapContentFromSession(t, session) + + assert.True(t, hasContent, "user should see their own heatmap content") +} + +func TestPrivateActivityYesHeatmapHasNoContentForOtherUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + testPrivateActivityHelperEnablePrivateActivity(t) + + session := loginUser(t, privateActivityTestOtherUser) + hasContent := testPrivateActivityHelperHasHeatmapContentFromSession(t, session) + + assert.False(t, hasContent, "other user should not see heatmap content") +} + +func TestPrivateActivityYesHeatmapHasNoContentForAdmin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + testPrivateActivityHelperEnablePrivateActivity(t) + + session := loginUser(t, privateActivityTestAdmin) + hasContent := testPrivateActivityHelperHasHeatmapContentFromSession(t, session) + + assert.True(t, hasContent, "heatmap should show content for admin") +} diff --git a/tests/integration/pull_compare_test.go b/tests/integration/pull_compare_test.go new file mode 100644 index 0000000000..7934b6e77c --- /dev/null +++ b/tests/integration/pull_compare_test.go @@ -0,0 +1,28 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/tests" + "github.com/stretchr/testify/assert" +) + +func TestPullCompare(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2/repo1/pulls") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + link, exists := htmlDoc.doc.Find(".ui.three.column.grid").Find(".ui.green.button").Attr("href") + assert.True(t, exists, "The template has changed") + + req = NewRequest(t, "GET", link) + resp = session.MakeRequest(t, req, http.StatusOK) + assert.EqualValues(t, http.StatusOK, resp.Code) +} diff --git a/tests/integration/pull_create_test.go b/tests/integration/pull_create_test.go new file mode 100644 index 0000000000..24c73ab4e9 --- /dev/null +++ b/tests/integration/pull_create_test.go @@ -0,0 +1,163 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/http/httptest" + "net/url" + "path" + "strings" + "testing" + + "code.gitea.io/gitea/tests" + "github.com/stretchr/testify/assert" +) + +func testPullCreate(t *testing.T, session *TestSession, user, repo, branch, title string) *httptest.ResponseRecorder { + req := NewRequest(t, "GET", path.Join(user, repo)) + resp := session.MakeRequest(t, req, http.StatusOK) + + // Click the PR button to create a pull + htmlDoc := NewHTMLParser(t, resp.Body) + link, exists := htmlDoc.doc.Find("#new-pull-request").Parent().Attr("href") + assert.True(t, exists, "The template has changed") + if branch != "master" { + link = strings.Replace(link, ":master", ":"+branch, 1) + } + + req = NewRequest(t, "GET", link) + resp = session.MakeRequest(t, req, http.StatusOK) + + // Submit the form for creating the pull + htmlDoc = NewHTMLParser(t, resp.Body) + link, exists = htmlDoc.doc.Find("form.ui.form").Attr("action") + assert.True(t, exists, "The template has changed") + req = NewRequestWithValues(t, "POST", link, map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "title": title, + }) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + + return resp +} + +func TestPullCreate(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + + // check the redirected URL + url := resp.Header().Get("Location") + assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url) + + // check .diff can be accessed and matches performed change + req := NewRequest(t, "GET", url+".diff") + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body) + assert.Regexp(t, "^diff", resp.Body) + assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one + + // check .patch can be accessed and matches performed change + req = NewRequest(t, "GET", url+".patch") + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body) + assert.Regexp(t, "diff", resp.Body) + assert.Regexp(t, `Subject: \[PATCH\] Update 'README.md'`, resp.Body) + assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one + }) +} + +func TestPullCreate_TitleEscape(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + resp := testPullCreate(t, session, "user1", "repo1", "master", "<i>XSS PR</i>") + + // check the redirected URL + url := resp.Header().Get("Location") + assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url) + + // Edit title + req := NewRequest(t, "GET", url) + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + editTestTitleURL, exists := htmlDoc.doc.Find("#save-edit-title").First().Attr("data-update-url") + assert.True(t, exists, "The template has changed") + + req = NewRequestWithValues(t, "POST", editTestTitleURL, map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "title": "<u>XSS PR</u>", + }) + session.MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", url) + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + titleHTML, err := htmlDoc.doc.Find(".comment-list .timeline-item.event .text b").First().Html() + assert.NoError(t, err) + assert.Equal(t, "<strike><i>XSS PR</i></strike>", titleHTML) + titleHTML, err = htmlDoc.doc.Find(".comment-list .timeline-item.event .text b").Next().Html() + assert.NoError(t, err) + assert.Equal(t, "<u>XSS PR</u>", titleHTML) + }) +} + +func testUIDeleteBranch(t *testing.T, session *TestSession, ownerName, repoName, branchName string) { + relURL := "/" + path.Join(ownerName, repoName, "branches") + req := NewRequest(t, "GET", relURL) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + req = NewRequestWithValues(t, "POST", relURL+"/delete", map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "name": branchName, + }) + session.MakeRequest(t, req, http.StatusOK) +} + +func testDeleteRepository(t *testing.T, session *TestSession, ownerName, repoName string) { + relURL := "/" + path.Join(ownerName, repoName, "settings") + req := NewRequest(t, "GET", relURL) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + req = NewRequestWithValues(t, "POST", relURL+"?action=delete", map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "repo_name": repoName, + }) + session.MakeRequest(t, req, http.StatusSeeOther) +} + +func TestPullBranchDelete(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testCreateBranch(t, session, "user1", "repo1", "branch/master", "master1", http.StatusSeeOther) + testEditFile(t, session, "user1", "repo1", "master1", "README.md", "Hello, World (Edited)\n") + resp := testPullCreate(t, session, "user1", "repo1", "master1", "This is a pull title") + + // check the redirected URL + url := resp.Header().Get("Location") + assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url) + req := NewRequest(t, "GET", url) + session.MakeRequest(t, req, http.StatusOK) + + // delete head branch and confirm pull page is ok + testUIDeleteBranch(t, session, "user1", "repo1", "master1") + req = NewRequest(t, "GET", url) + session.MakeRequest(t, req, http.StatusOK) + + // delete head repository and confirm pull page is ok + testDeleteRepository(t, session, "user1", "repo1") + req = NewRequest(t, "GET", url) + session.MakeRequest(t, req, http.StatusOK) + }) +} diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go new file mode 100644 index 0000000000..335dae4b38 --- /dev/null +++ b/tests/integration/pull_merge_test.go @@ -0,0 +1,423 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "bytes" + "context" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path" + "strings" + "testing" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/git" + repo_module "code.gitea.io/gitea/modules/repository" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/services/pull" + repo_service "code.gitea.io/gitea/services/repository" + files_service "code.gitea.io/gitea/services/repository/files" + + "github.com/stretchr/testify/assert" +) + +func testPullMerge(t *testing.T, session *TestSession, user, repo, pullnum string, mergeStyle repo_model.MergeStyle) *httptest.ResponseRecorder { + req := NewRequest(t, "GET", path.Join(user, repo, "pulls", pullnum)) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + link := path.Join(user, repo, "pulls", pullnum, "merge") + req = NewRequestWithValues(t, "POST", link, map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "do": string(mergeStyle), + }) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + + return resp +} + +func testPullCleanUp(t *testing.T, session *TestSession, user, repo, pullnum string) *httptest.ResponseRecorder { + req := NewRequest(t, "GET", path.Join(user, repo, "pulls", pullnum)) + resp := session.MakeRequest(t, req, http.StatusOK) + + // Click the little green button to create a pull + htmlDoc := NewHTMLParser(t, resp.Body) + link, exists := htmlDoc.doc.Find(".timeline-item .delete-button").Attr("data-url") + assert.True(t, exists, "The template has changed, can not find delete button url") + req = NewRequestWithValues(t, "POST", link, map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + }) + resp = session.MakeRequest(t, req, http.StatusOK) + + return resp +} + +func TestPullMerge(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + hookTasks, err := webhook.HookTasks(1, 1) // Retrieve previous hook number + assert.NoError(t, err) + hookTasksLenBefore := len(hookTasks) + + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + + resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + + elem := strings.Split(test.RedirectURL(resp), "/") + assert.EqualValues(t, "pulls", elem[3]) + testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge) + + hookTasks, err = webhook.HookTasks(1, 1) + assert.NoError(t, err) + assert.Len(t, hookTasks, hookTasksLenBefore+1) + }) +} + +func TestPullRebase(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + hookTasks, err := webhook.HookTasks(1, 1) // Retrieve previous hook number + assert.NoError(t, err) + hookTasksLenBefore := len(hookTasks) + + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + + resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + + elem := strings.Split(test.RedirectURL(resp), "/") + assert.EqualValues(t, "pulls", elem[3]) + testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebase) + + hookTasks, err = webhook.HookTasks(1, 1) + assert.NoError(t, err) + assert.Len(t, hookTasks, hookTasksLenBefore+1) + }) +} + +func TestPullRebaseMerge(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + hookTasks, err := webhook.HookTasks(1, 1) // Retrieve previous hook number + assert.NoError(t, err) + hookTasksLenBefore := len(hookTasks) + + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + + resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + + elem := strings.Split(test.RedirectURL(resp), "/") + assert.EqualValues(t, "pulls", elem[3]) + testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebaseMerge) + + hookTasks, err = webhook.HookTasks(1, 1) + assert.NoError(t, err) + assert.Len(t, hookTasks, hookTasksLenBefore+1) + }) +} + +func TestPullSquash(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + hookTasks, err := webhook.HookTasks(1, 1) // Retrieve previous hook number + assert.NoError(t, err) + hookTasksLenBefore := len(hookTasks) + + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited!)\n") + + resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + + elem := strings.Split(test.RedirectURL(resp), "/") + assert.EqualValues(t, "pulls", elem[3]) + testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleSquash) + + hookTasks, err = webhook.HookTasks(1, 1) + assert.NoError(t, err) + assert.Len(t, hookTasks, hookTasksLenBefore+1) + }) +} + +func TestPullCleanUpAfterMerge(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited - TestPullCleanUpAfterMerge)\n") + + resp := testPullCreate(t, session, "user1", "repo1", "feature/test", "This is a pull title") + + elem := strings.Split(test.RedirectURL(resp), "/") + assert.EqualValues(t, "pulls", elem[3]) + testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge) + + // Check PR branch deletion + resp = testPullCleanUp(t, session, elem[1], elem[2], elem[4]) + respJSON := struct { + Redirect string + }{} + DecodeJSON(t, resp, &respJSON) + + assert.NotEmpty(t, respJSON.Redirect, "Redirected URL is not found") + + elem = strings.Split(respJSON.Redirect, "/") + assert.EqualValues(t, "pulls", elem[3]) + + // Check branch deletion result + req := NewRequest(t, "GET", respJSON.Redirect) + resp = session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + resultMsg := htmlDoc.doc.Find(".ui.message>p").Text() + + assert.EqualValues(t, "Branch 'user1/feature/test' has been deleted.", resultMsg) + }) +} + +func TestCantMergeWorkInProgress(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + + resp := testPullCreate(t, session, "user1", "repo1", "master", "[wip] This is a pull title") + + req := NewRequest(t, "GET", resp.Header().Get("Location")) + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + text := strings.TrimSpace(htmlDoc.doc.Find(".merge-section > .item").Last().Text()) + assert.NotEmpty(t, text, "Can't find WIP text") + + assert.Contains(t, text, translation.NewLocale("en-US").Tr("repo.pulls.cannot_merge_work_in_progress"), "Unable to find WIP text") + assert.Contains(t, text, "[wip]", "Unable to find WIP text") + }) +} + +func TestCantMergeConflict(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n") + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base", "README.md", "Hello, World (Edited Twice)\n") + + // Use API to create a conflicting pr + token := getTokenForLoggedInUser(t, session) + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", "user1", "repo1", token), &api.CreatePullRequestOption{ + Head: "conflict", + Base: "base", + Title: "create a conflicting pr", + }) + session.MakeRequest(t, req, http.StatusCreated) + + // Now this PR will be marked conflict - or at least a race will do - so drop down to pure code at this point... + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: "user1", + }) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ + OwnerID: user1.ID, + Name: "repo1", + }) + + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ + HeadRepoID: repo1.ID, + BaseRepoID: repo1.ID, + HeadBranch: "conflict", + BaseBranch: "base", + }) + + gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name)) + assert.NoError(t, err) + + err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "CONFLICT") + assert.Error(t, err, "Merge should return an error due to conflict") + assert.True(t, models.IsErrMergeConflicts(err), "Merge error is not a conflict error") + + err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleRebase, "", "CONFLICT") + assert.Error(t, err, "Merge should return an error due to conflict") + assert.True(t, models.IsErrRebaseConflicts(err), "Merge error is not a conflict error") + gitRepo.Close() + }) +} + +func TestCantMergeUnrelated(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base", "README.md", "Hello, World (Edited Twice)\n") + + // Now we want to create a commit on a branch that is totally unrelated to our current head + // Drop down to pure code at this point + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: "user1", + }) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ + OwnerID: user1.ID, + Name: "repo1", + }) + path := repo_model.RepoPath(user1.Name, repo1.Name) + + err := git.NewCommand(git.DefaultContext, "read-tree", "--empty").Run(&git.RunOpts{Dir: path}) + assert.NoError(t, err) + + stdin := bytes.NewBufferString("Unrelated File") + var stdout strings.Builder + err = git.NewCommand(git.DefaultContext, "hash-object", "-w", "--stdin").Run(&git.RunOpts{ + Dir: path, + Stdin: stdin, + Stdout: &stdout, + }) + + assert.NoError(t, err) + sha := strings.TrimSpace(stdout.String()) + + _, _, err = git.NewCommand(git.DefaultContext, "update-index", "--add", "--replace", "--cacheinfo", "100644", sha, "somewher-over-the-rainbow").RunStdString(&git.RunOpts{Dir: path}) + assert.NoError(t, err) + + treeSha, _, err := git.NewCommand(git.DefaultContext, "write-tree").RunStdString(&git.RunOpts{Dir: path}) + assert.NoError(t, err) + treeSha = strings.TrimSpace(treeSha) + + commitTimeStr := time.Now().Format(time.RFC3339) + doerSig := user1.NewGitSig() + env := append(os.Environ(), + "GIT_AUTHOR_NAME="+doerSig.Name, + "GIT_AUTHOR_EMAIL="+doerSig.Email, + "GIT_AUTHOR_DATE="+commitTimeStr, + "GIT_COMMITTER_NAME="+doerSig.Name, + "GIT_COMMITTER_EMAIL="+doerSig.Email, + "GIT_COMMITTER_DATE="+commitTimeStr, + ) + + messageBytes := new(bytes.Buffer) + _, _ = messageBytes.WriteString("Unrelated") + _, _ = messageBytes.WriteString("\n") + + stdout.Reset() + err = git.NewCommand(git.DefaultContext, "commit-tree", treeSha). + Run(&git.RunOpts{ + Env: env, + Dir: path, + Stdin: messageBytes, + Stdout: &stdout, + }) + assert.NoError(t, err) + commitSha := strings.TrimSpace(stdout.String()) + + _, _, err = git.NewCommand(git.DefaultContext, "branch", "unrelated", commitSha).RunStdString(&git.RunOpts{Dir: path}) + assert.NoError(t, err) + + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n") + + // Use API to create a conflicting pr + token := getTokenForLoggedInUser(t, session) + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", "user1", "repo1", token), &api.CreatePullRequestOption{ + Head: "unrelated", + Base: "base", + Title: "create an unrelated pr", + }) + session.MakeRequest(t, req, http.StatusCreated) + + // Now this PR could be marked conflict - or at least a race may occur - so drop down to pure code at this point... + gitRepo, err := git.OpenRepository(git.DefaultContext, path) + assert.NoError(t, err) + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ + HeadRepoID: repo1.ID, + BaseRepoID: repo1.ID, + HeadBranch: "unrelated", + BaseBranch: "base", + }) + + err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "UNRELATED") + assert.Error(t, err, "Merge should return an error due to unrelated") + assert.True(t, models.IsErrMergeUnrelatedHistories(err), "Merge error is not a unrelated histories error") + gitRepo.Close() + }) +} + +func TestConflictChecking(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + // Create new clean repo to test conflict checking. + baseRepo, err := repo_service.CreateRepository(user, user, repo_module.CreateRepoOptions{ + Name: "conflict-checking", + Description: "Tempo repo", + AutoInit: true, + Readme: "Default", + DefaultBranch: "main", + }) + assert.NoError(t, err) + assert.NotEmpty(t, baseRepo) + + // create a commit on new branch. + _, err = files_service.CreateOrUpdateRepoFile(git.DefaultContext, baseRepo, user, &files_service.UpdateRepoFileOptions{ + TreePath: "important_file", + Message: "Add a important file", + Content: "Just a non-important file", + IsNewFile: true, + OldBranch: "main", + NewBranch: "important-secrets", + }) + assert.NoError(t, err) + + // create a commit on main branch. + _, err = files_service.CreateOrUpdateRepoFile(git.DefaultContext, baseRepo, user, &files_service.UpdateRepoFileOptions{ + TreePath: "important_file", + Message: "Add a important file", + Content: "Not the same content :P", + IsNewFile: true, + OldBranch: "main", + NewBranch: "main", + }) + assert.NoError(t, err) + + // create Pull to merge the important-secrets branch into main branch. + pullIssue := &issues_model.Issue{ + RepoID: baseRepo.ID, + Title: "PR with conflict!", + PosterID: user.ID, + Poster: user, + IsPull: true, + } + + pullRequest := &issues_model.PullRequest{ + HeadRepoID: baseRepo.ID, + BaseRepoID: baseRepo.ID, + HeadBranch: "important-secrets", + BaseBranch: "main", + HeadRepo: baseRepo, + BaseRepo: baseRepo, + Type: issues_model.PullRequestGitea, + } + err = pull.NewPullRequest(git.DefaultContext, baseRepo, pullIssue, nil, nil, pullRequest, nil) + assert.NoError(t, err) + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: "PR with conflict!"}) + conflictingPR, err := issues_model.GetPullRequestByIssueID(db.DefaultContext, issue.ID) + assert.NoError(t, err) + + // Ensure conflictedFiles is populated. + assert.Equal(t, 1, len(conflictingPR.ConflictedFiles)) + // Check if status is correct. + assert.Equal(t, issues_model.PullRequestStatusConflict, conflictingPR.Status) + // Ensure that mergeable returns false + assert.False(t, conflictingPR.Mergeable()) + }) +} diff --git a/tests/integration/pull_review_test.go b/tests/integration/pull_review_test.go new file mode 100644 index 0000000000..d713c0f858 --- /dev/null +++ b/tests/integration/pull_review_test.go @@ -0,0 +1,22 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/tests" +) + +func TestPullView_ReviewerMissed(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user1") + + req := NewRequest(t, "GET", "/pulls") + session.MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", "/user2/repo1/pulls/3") + session.MakeRequest(t, req, http.StatusOK) +} diff --git a/tests/integration/pull_status_test.go b/tests/integration/pull_status_test.go new file mode 100644 index 0000000000..0f9cd41ec2 --- /dev/null +++ b/tests/integration/pull_status_test.go @@ -0,0 +1,157 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +package integration + +import ( + "fmt" + "net/http" + "net/url" + "path" + "strings" + "testing" + + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func TestPullCreate_CommitStatus(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1") + + url := path.Join("user1", "repo1", "compare", "master...status1") + req := NewRequestWithValues(t, "POST", url, + map[string]string{ + "_csrf": GetCSRF(t, session, url), + "title": "pull request from status1", + }, + ) + session.MakeRequest(t, req, http.StatusSeeOther) + + req = NewRequest(t, "GET", "/user1/repo1/pulls") + resp := session.MakeRequest(t, req, http.StatusOK) + NewHTMLParser(t, resp.Body) + + // Request repository commits page + req = NewRequest(t, "GET", "/user1/repo1/pulls/1/commits") + resp = session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + + // Get first commit URL + commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href") + assert.True(t, exists) + assert.NotEmpty(t, commitURL) + + commitID := path.Base(commitURL) + + statusList := []api.CommitStatusState{ + api.CommitStatusPending, + api.CommitStatusError, + api.CommitStatusFailure, + api.CommitStatusWarning, + api.CommitStatusSuccess, + } + + statesIcons := map[api.CommitStatusState]string{ + api.CommitStatusPending: "octicon-dot-fill", + api.CommitStatusSuccess: "octicon-check", + api.CommitStatusError: "gitea-exclamation", + api.CommitStatusFailure: "octicon-x", + api.CommitStatusWarning: "gitea-exclamation", + } + + testCtx := NewAPITestContext(t, "user1", "repo1") + + // Update commit status, and check if icon is updated as well + for _, status := range statusList { + + // Call API to add status for commit + t.Run("CreateStatus", doAPICreateCommitStatus(testCtx, commitID, status)) + + req = NewRequestf(t, "GET", "/user1/repo1/pulls/1/commits") + resp = session.MakeRequest(t, req, http.StatusOK) + doc = NewHTMLParser(t, resp.Body) + + commitURL, exists = doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href") + assert.True(t, exists) + assert.NotEmpty(t, commitURL) + assert.EqualValues(t, commitID, path.Base(commitURL)) + + cls, ok := doc.doc.Find("#commits-table tbody tr td.message .commit-status").Last().Attr("class") + assert.True(t, ok) + assert.Contains(t, cls, statesIcons[status]) + } + }) +} + +func doAPICreateCommitStatus(ctx APITestContext, commitID string, status api.CommitStatusState) func(*testing.T) { + return func(t *testing.T) { + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/statuses/%s?token=%s", ctx.Username, ctx.Reponame, commitID, ctx.Token), + api.CreateStatusOption{ + State: status, + TargetURL: "http://test.ci/", + Description: "", + Context: "testci", + }, + ) + if ctx.ExpectedCode != 0 { + ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) + return + } + ctx.Session.MakeRequest(t, req, http.StatusCreated) + } +} + +func TestPullCreate_EmptyChangesWithDifferentCommits(t *testing.T) { + // Merge must continue if commits SHA are different, even if content is same + // Reason: gitflow and merging master back into develop, where is high possiblity, there are no changes + // but just commit saying "Merge branch". And this meta commit can be also tagged, + // so we need to have this meta commit also in develop branch. + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1") + testEditFileToNewBranch(t, session, "user1", "repo1", "status1", "status1", "README.md", "# repo1\n\nDescription for repo1") + + url := path.Join("user1", "repo1", "compare", "master...status1") + req := NewRequestWithValues(t, "POST", url, + map[string]string{ + "_csrf": GetCSRF(t, session, url), + "title": "pull request from status1", + }, + ) + session.MakeRequest(t, req, http.StatusSeeOther) + + req = NewRequest(t, "GET", "/user1/repo1/pulls/1") + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + + text := strings.TrimSpace(doc.doc.Find(".merge-section").Text()) + assert.Contains(t, text, "This pull request can be merged automatically.") + }) +} + +func TestPullCreate_EmptyChangesWithSameCommits(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testCreateBranch(t, session, "user1", "repo1", "branch/master", "status1", http.StatusSeeOther) + url := path.Join("user1", "repo1", "compare", "master...status1") + req := NewRequestWithValues(t, "POST", url, + map[string]string{ + "_csrf": GetCSRF(t, session, url), + "title": "pull request from status1", + }, + ) + session.MakeRequest(t, req, http.StatusSeeOther) + req = NewRequest(t, "GET", "/user1/repo1/pulls/1") + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + + text := strings.TrimSpace(doc.doc.Find(".merge-section").Text()) + assert.Contains(t, text, "This branch is already included in the target branch. There is nothing to merge.") + }) +} diff --git a/tests/integration/pull_update_test.go b/tests/integration/pull_update_test.go new file mode 100644 index 0000000000..c08faaaeb6 --- /dev/null +++ b/tests/integration/pull_update_test.go @@ -0,0 +1,174 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "testing" + "time" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + repo_module "code.gitea.io/gitea/modules/repository" + pull_service "code.gitea.io/gitea/services/pull" + repo_service "code.gitea.io/gitea/services/repository" + files_service "code.gitea.io/gitea/services/repository/files" + + "github.com/stretchr/testify/assert" +) + +func TestAPIPullUpdate(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // Create PR to test + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26}) + pr := createOutdatedPR(t, user, org26) + + // Test GetDiverging + diffCount, err := pull_service.GetDiverging(git.DefaultContext, pr) + assert.NoError(t, err) + assert.EqualValues(t, 1, diffCount.Behind) + assert.EqualValues(t, 1, diffCount.Ahead) + assert.NoError(t, pr.LoadBaseRepo()) + assert.NoError(t, pr.LoadIssue()) + + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?token="+token, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index) + session.MakeRequest(t, req, http.StatusOK) + + // Test GetDiverging after update + diffCount, err = pull_service.GetDiverging(git.DefaultContext, pr) + assert.NoError(t, err) + assert.EqualValues(t, 0, diffCount.Behind) + assert.EqualValues(t, 2, diffCount.Ahead) + }) +} + +func TestAPIPullUpdateByRebase(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // Create PR to test + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26}) + pr := createOutdatedPR(t, user, org26) + + // Test GetDiverging + diffCount, err := pull_service.GetDiverging(git.DefaultContext, pr) + assert.NoError(t, err) + assert.EqualValues(t, 1, diffCount.Behind) + assert.EqualValues(t, 1, diffCount.Ahead) + assert.NoError(t, pr.LoadBaseRepo()) + assert.NoError(t, pr.LoadIssue()) + + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?style=rebase&token="+token, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index) + session.MakeRequest(t, req, http.StatusOK) + + // Test GetDiverging after update + diffCount, err = pull_service.GetDiverging(git.DefaultContext, pr) + assert.NoError(t, err) + assert.EqualValues(t, 0, diffCount.Behind) + assert.EqualValues(t, 1, diffCount.Ahead) + }) +} + +func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_model.PullRequest { + baseRepo, err := repo_service.CreateRepository(actor, actor, repo_module.CreateRepoOptions{ + Name: "repo-pr-update", + Description: "repo-tmp-pr-update description", + AutoInit: true, + Gitignores: "C,C++", + License: "MIT", + Readme: "Default", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, baseRepo) + + headRepo, err := repo_service.ForkRepository(git.DefaultContext, actor, forkOrg, repo_service.ForkRepoOptions{ + BaseRepo: baseRepo, + Name: "repo-pr-update", + Description: "desc", + }) + assert.NoError(t, err) + assert.NotEmpty(t, headRepo) + + // create a commit on base Repo + _, err = files_service.CreateOrUpdateRepoFile(git.DefaultContext, baseRepo, actor, &files_service.UpdateRepoFileOptions{ + TreePath: "File_A", + Message: "Add File A", + Content: "File A", + IsNewFile: true, + OldBranch: "master", + NewBranch: "master", + Author: &files_service.IdentityOptions{ + Name: actor.Name, + Email: actor.Email, + }, + Committer: &files_service.IdentityOptions{ + Name: actor.Name, + Email: actor.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + + // create a commit on head Repo + _, err = files_service.CreateOrUpdateRepoFile(git.DefaultContext, headRepo, actor, &files_service.UpdateRepoFileOptions{ + TreePath: "File_B", + Message: "Add File on PR branch", + Content: "File B", + IsNewFile: true, + OldBranch: "master", + NewBranch: "newBranch", + Author: &files_service.IdentityOptions{ + Name: actor.Name, + Email: actor.Email, + }, + Committer: &files_service.IdentityOptions{ + Name: actor.Name, + Email: actor.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + + // create Pull + pullIssue := &issues_model.Issue{ + RepoID: baseRepo.ID, + Title: "Test Pull -to-update-", + PosterID: actor.ID, + Poster: actor, + IsPull: true, + } + pullRequest := &issues_model.PullRequest{ + HeadRepoID: headRepo.ID, + BaseRepoID: baseRepo.ID, + HeadBranch: "newBranch", + BaseBranch: "master", + HeadRepo: headRepo, + BaseRepo: baseRepo, + Type: issues_model.PullRequestGitea, + } + err = pull_service.NewPullRequest(git.DefaultContext, baseRepo, pullIssue, nil, nil, pullRequest, nil) + assert.NoError(t, err) + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: "Test Pull -to-update-"}) + pr, err := issues_model.GetPullRequestByIssueID(db.DefaultContext, issue.ID) + assert.NoError(t, err) + + return pr +} diff --git a/tests/integration/release_test.go b/tests/integration/release_test.go new file mode 100644 index 0000000000..2a52a5cde2 --- /dev/null +++ b/tests/integration/release_test.go @@ -0,0 +1,213 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + "time" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/tests" + + "github.com/PuerkitoBio/goquery" + "github.com/stretchr/testify/assert" +) + +func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title string, preRelease, draft bool) { + req := NewRequest(t, "GET", repoURL+"/releases/new") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + link, exists := htmlDoc.doc.Find("form.ui.form").Attr("action") + assert.True(t, exists, "The template has changed") + + postData := map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "tag_name": tag, + "tag_target": "master", + "title": title, + "content": "", + } + if preRelease { + postData["prerelease"] = "on" + } + if draft { + postData["draft"] = "Save Draft" + } + req = NewRequestWithValues(t, "POST", link, postData) + + resp = session.MakeRequest(t, req, http.StatusSeeOther) + + test.RedirectURL(resp) // check that redirect URL exists +} + +func checkLatestReleaseAndCount(t *testing.T, session *TestSession, repoURL, version, label string, count int) { + req := NewRequest(t, "GET", repoURL+"/releases") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + labelText := htmlDoc.doc.Find("#release-list > li .meta .label").First().Text() + assert.EqualValues(t, label, labelText) + titleText := htmlDoc.doc.Find("#release-list > li .detail h4 a").First().Text() + assert.EqualValues(t, version, titleText) + + releaseList := htmlDoc.doc.Find("#release-list > li") + assert.EqualValues(t, count, releaseList.Length()) +} + +func TestViewReleases(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2/repo1/releases") + session.MakeRequest(t, req, http.StatusOK) + + // if CI is to slow this test fail, so lets wait a bit + time.Sleep(time.Millisecond * 100) +} + +func TestViewReleasesNoLogin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/user2/repo1/releases") + MakeRequest(t, req, http.StatusOK) +} + +func TestCreateRelease(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, false) + + checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").Tr("repo.release.stable"), 4) +} + +func TestCreateReleasePreRelease(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", true, false) + + checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").Tr("repo.release.prerelease"), 4) +} + +func TestCreateReleaseDraft(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, true) + + checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").Tr("repo.release.draft"), 4) +} + +func TestCreateReleasePaging(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + oldAPIDefaultNum := setting.API.DefaultPagingNum + defer func() { + setting.API.DefaultPagingNum = oldAPIDefaultNum + }() + setting.API.DefaultPagingNum = 10 + + session := loginUser(t, "user2") + // Create enough releases to have paging + for i := 0; i < 12; i++ { + version := fmt.Sprintf("v0.0.%d", i) + createNewRelease(t, session, "/user2/repo1", version, version, false, false) + } + createNewRelease(t, session, "/user2/repo1", "v0.0.12", "v0.0.12", false, true) + + checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.12", translation.NewLocale("en-US").Tr("repo.release.draft"), 10) + + // Check that user4 does not see draft and still see 10 latest releases + session2 := loginUser(t, "user4") + checkLatestReleaseAndCount(t, session2, "/user2/repo1", "v0.0.11", translation.NewLocale("en-US").Tr("repo.release.stable"), 10) +} + +func TestViewReleaseListNoLogin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + link := repo.Link() + "/releases" + + req := NewRequest(t, "GET", link) + rsp := MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, rsp.Body) + releases := htmlDoc.Find("#release-list li.ui.grid") + assert.Equal(t, 2, releases.Length()) + + links := make([]string, 0, 5) + releases.Each(func(i int, s *goquery.Selection) { + link, exist := s.Find(".release-list-title a").Attr("href") + if !exist { + return + } + links = append(links, link) + }) + + assert.EqualValues(t, []string{"/user2/repo1/releases/tag/v1.0", "/user2/repo1/releases/tag/v1.1"}, links) +} + +func TestViewReleaseListLogin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + link := repo.Link() + "/releases" + + session := loginUser(t, "user1") + req := NewRequest(t, "GET", link) + rsp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, rsp.Body) + releases := htmlDoc.Find("#release-list li.ui.grid") + assert.Equal(t, 3, releases.Length()) + + links := make([]string, 0, 5) + releases.Each(func(i int, s *goquery.Selection) { + link, exist := s.Find(".release-list-title a").Attr("href") + if !exist { + return + } + links = append(links, link) + }) + + assert.EqualValues(t, []string{ + "/user2/repo1/releases/tag/draft-release", + "/user2/repo1/releases/tag/v1.0", + "/user2/repo1/releases/tag/v1.1", + }, links) +} + +func TestViewTagsList(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + link := repo.Link() + "/tags" + + session := loginUser(t, "user1") + req := NewRequest(t, "GET", link) + rsp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, rsp.Body) + tags := htmlDoc.Find(".tag-list tr") + assert.Equal(t, 3, tags.Length()) + + tagNames := make([]string, 0, 5) + tags.Each(func(i int, s *goquery.Selection) { + tagNames = append(tagNames, s.Find(".tag a.df.ac").Text()) + }) + + assert.EqualValues(t, []string{"v1.0", "delete-tag", "v1.1"}, tagNames) +} diff --git a/tests/integration/rename_branch_test.go b/tests/integration/rename_branch_test.go new file mode 100644 index 0000000000..9ea69702af --- /dev/null +++ b/tests/integration/rename_branch_test.go @@ -0,0 +1,45 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestRenameBranch(t *testing.T) { + // get branch setting page + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2/repo1/settings/branches") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + postData := map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "from": "master", + "to": "main", + } + req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/rename_branch", postData) + session.MakeRequest(t, req, http.StatusSeeOther) + + // check new branch link + req = NewRequestWithValues(t, "GET", "/user2/repo1/src/branch/main/README.md", postData) + session.MakeRequest(t, req, http.StatusOK) + + // check old branch link + req = NewRequestWithValues(t, "GET", "/user2/repo1/src/branch/master/README.md", postData) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + location := resp.HeaderMap.Get("Location") + assert.Equal(t, "/user2/repo1/src/branch/main/README.md", location) + + // check db + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + assert.Equal(t, "main", repo1.DefaultBranch) +} diff --git a/tests/integration/repo_activity_test.go b/tests/integration/repo_activity_test.go new file mode 100644 index 0000000000..ea8845ac39 --- /dev/null +++ b/tests/integration/repo_activity_test.go @@ -0,0 +1,66 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "strings" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/test" + + "github.com/stretchr/testify/assert" +) + +func TestRepoActivity(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + session := loginUser(t, "user1") + + // Create PRs (1 merged & 2 proposed) + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + elem := strings.Split(test.RedirectURL(resp), "/") + assert.EqualValues(t, "pulls", elem[3]) + testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge) + + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/better_readme", "README.md", "Hello, World (Edited Again)\n") + testPullCreate(t, session, "user1", "repo1", "feat/better_readme", "This is a pull title") + + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/much_better_readme", "README.md", "Hello, World (Edited More)\n") + testPullCreate(t, session, "user1", "repo1", "feat/much_better_readme", "This is a pull title") + + // Create issues (3 new issues) + testNewIssue(t, session, "user2", "repo1", "Issue 1", "Description 1") + testNewIssue(t, session, "user2", "repo1", "Issue 2", "Description 2") + testNewIssue(t, session, "user2", "repo1", "Issue 3", "Description 3") + + // Create releases (1 new release) + createNewRelease(t, session, "/user2/repo1", "v1.0.0", "v1.0.0", false, false) + + // Open Activity page and check stats + req := NewRequest(t, "GET", "/user2/repo1/activity") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + // Should be 1 published release + list := htmlDoc.doc.Find("#published-releases").Next().Find("p.desc") + assert.Len(t, list.Nodes, 1) + + // Should be 1 merged pull request + list = htmlDoc.doc.Find("#merged-pull-requests").Next().Find("p.desc") + assert.Len(t, list.Nodes, 1) + + // Should be 2 proposed pull requests + list = htmlDoc.doc.Find("#proposed-pull-requests").Next().Find("p.desc") + assert.Len(t, list.Nodes, 2) + + // Should be 3 new issues + list = htmlDoc.doc.Find("#new-issues").Next().Find("p.desc") + assert.Len(t, list.Nodes, 3) + }) +} diff --git a/tests/integration/repo_branch_test.go b/tests/integration/repo_branch_test.go new file mode 100644 index 0000000000..96ffa5a46e --- /dev/null +++ b/tests/integration/repo_branch_test.go @@ -0,0 +1,148 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "path" + "strings" + "testing" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubURL, newBranchName string, expectedStatus int) string { + var csrf string + if expectedStatus == http.StatusNotFound { + csrf = GetCSRF(t, session, path.Join(user, repo, "src/branch/master")) + } else { + csrf = GetCSRF(t, session, path.Join(user, repo, "src", oldRefSubURL)) + } + req := NewRequestWithValues(t, "POST", path.Join(user, repo, "branches/_new", oldRefSubURL), map[string]string{ + "_csrf": csrf, + "new_branch_name": newBranchName, + }) + resp := session.MakeRequest(t, req, expectedStatus) + if expectedStatus != http.StatusSeeOther { + return "" + } + return test.RedirectURL(resp) +} + +func TestCreateBranch(t *testing.T) { + onGiteaRun(t, testCreateBranches) +} + +func testCreateBranches(t *testing.T, giteaURL *url.URL) { + tests := []struct { + OldRefSubURL string + NewBranch string + CreateRelease string + FlashMessage string + ExpectedStatus int + }{ + { + OldRefSubURL: "branch/master", + NewBranch: "feature/test1", + ExpectedStatus: http.StatusSeeOther, + FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test1"), + }, + { + OldRefSubURL: "branch/master", + NewBranch: "", + ExpectedStatus: http.StatusSeeOther, + FlashMessage: translation.NewLocale("en-US").Tr("form.NewBranchName") + translation.NewLocale("en-US").Tr("form.require_error"), + }, + { + OldRefSubURL: "branch/master", + NewBranch: "feature=test1", + ExpectedStatus: http.StatusSeeOther, + FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature=test1"), + }, + { + OldRefSubURL: "branch/master", + NewBranch: strings.Repeat("b", 101), + ExpectedStatus: http.StatusSeeOther, + FlashMessage: translation.NewLocale("en-US").Tr("form.NewBranchName") + translation.NewLocale("en-US").Tr("form.max_size_error", "100"), + }, + { + OldRefSubURL: "branch/master", + NewBranch: "master", + ExpectedStatus: http.StatusSeeOther, + FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.branch_already_exists", "master"), + }, + { + OldRefSubURL: "branch/master", + NewBranch: "master/test", + ExpectedStatus: http.StatusSeeOther, + FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.branch_name_conflict", "master/test", "master"), + }, + { + OldRefSubURL: "commit/acd1d892867872cb47f3993468605b8aa59aa2e0", + NewBranch: "feature/test2", + ExpectedStatus: http.StatusNotFound, + }, + { + OldRefSubURL: "commit/65f1bf27bc3bf70f64657658635e66094edbcb4d", + NewBranch: "feature/test3", + ExpectedStatus: http.StatusSeeOther, + FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test3"), + }, + { + OldRefSubURL: "branch/master", + NewBranch: "v1.0.0", + CreateRelease: "v1.0.0", + ExpectedStatus: http.StatusSeeOther, + FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.tag_collision", "v1.0.0"), + }, + { + OldRefSubURL: "tag/v1.0.0", + NewBranch: "feature/test4", + CreateRelease: "v1.0.1", + ExpectedStatus: http.StatusSeeOther, + FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test4"), + }, + } + for _, test := range tests { + session := loginUser(t, "user2") + if test.CreateRelease != "" { + createNewRelease(t, session, "/user2/repo1", test.CreateRelease, test.CreateRelease, false, false) + } + redirectURL := testCreateBranch(t, session, "user2", "repo1", test.OldRefSubURL, test.NewBranch, test.ExpectedStatus) + if test.ExpectedStatus == http.StatusSeeOther { + req := NewRequest(t, "GET", redirectURL) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + assert.Equal(t, + test.FlashMessage, + strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()), + ) + } + } +} + +func TestCreateBranchInvalidCSRF(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + req := NewRequestWithValues(t, "POST", "user2/repo1/branches/_new/branch/master", map[string]string{ + "_csrf": "fake_csrf", + "new_branch_name": "test", + }) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + loc := resp.Header().Get("Location") + assert.Equal(t, setting.AppSubURL+"/", loc) + resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + assert.Equal(t, + "Bad Request: invalid CSRF token", + strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()), + ) +} diff --git a/tests/integration/repo_commits_search_test.go b/tests/integration/repo_commits_search_test.go new file mode 100644 index 0000000000..75e692f0ab --- /dev/null +++ b/tests/integration/repo_commits_search_test.go @@ -0,0 +1,43 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "strings" + "testing" + + "code.gitea.io/gitea/tests" + "github.com/stretchr/testify/assert" +) + +func testRepoCommitsSearch(t *testing.T, query, commit string) { + session := loginUser(t, "user2") + + // Request repository commits page + req := NewRequestf(t, "GET", "/user2/commits_search_test/commits/branch/master/search?q=%s", url.QueryEscape(query)) + resp := session.MakeRequest(t, req, http.StatusOK) + + doc := NewHTMLParser(t, resp.Body) + sel := doc.doc.Find("#commits-table tbody tr td.sha a") + assert.EqualValues(t, commit, strings.TrimSpace(sel.Text())) +} + +func TestRepoCommitsSearch(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testRepoCommitsSearch(t, "e8eabd", "") + testRepoCommitsSearch(t, "38a9cb", "") + testRepoCommitsSearch(t, "6e8e", "6e8eabd9a7") + testRepoCommitsSearch(t, "58e97", "58e97d1a24") + testRepoCommitsSearch(t, "author:alice", "6e8eabd9a7") + testRepoCommitsSearch(t, "author:alice 6e8ea", "6e8eabd9a7") + testRepoCommitsSearch(t, "committer:Tom", "58e97d1a24") + testRepoCommitsSearch(t, "author:bob commit-4", "58e97d1a24") + testRepoCommitsSearch(t, "author:bob commit after:2019-03-03", "58e97d1a24") + testRepoCommitsSearch(t, "committer:alice 6e8e before:2019-03-02", "6e8eabd9a7") + testRepoCommitsSearch(t, "committer:alice commit before:2019-03-02", "6e8eabd9a7") + testRepoCommitsSearch(t, "committer:alice author:tom commit before:2019-03-04 after:2019-03-02", "0a8499a22a") +} diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go new file mode 100644 index 0000000000..c9e7753596 --- /dev/null +++ b/tests/integration/repo_commits_test.go @@ -0,0 +1,117 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/http/httptest" + "path" + "testing" + + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestRepoCommits(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + + // Request repository commits page + req := NewRequest(t, "GET", "/user2/repo1/commits/branch/master") + resp := session.MakeRequest(t, req, http.StatusOK) + + doc := NewHTMLParser(t, resp.Body) + commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Attr("href") + assert.True(t, exists) + assert.NotEmpty(t, commitURL) +} + +func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + + // Request repository commits page + req := NewRequest(t, "GET", "/user2/repo1/commits/branch/master") + resp := session.MakeRequest(t, req, http.StatusOK) + + doc := NewHTMLParser(t, resp.Body) + // Get first commit URL + commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Attr("href") + assert.True(t, exists) + assert.NotEmpty(t, commitURL) + + // Call API to add status for commit + t.Run("CreateStatus", doAPICreateCommitStatus(NewAPITestContext(t, "user2", "repo1"), path.Base(commitURL), api.CommitStatusState(state))) + + req = NewRequest(t, "GET", "/user2/repo1/commits/branch/master") + resp = session.MakeRequest(t, req, http.StatusOK) + + doc = NewHTMLParser(t, resp.Body) + // Check if commit status is displayed in message column + sel := doc.doc.Find("#commits-table tbody tr td.message a.commit-statuses-trigger .commit-status") + assert.Equal(t, 1, sel.Length()) + for _, class := range classes { + assert.True(t, sel.HasClass(class)) + } + + // By SHA + req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/"+path.Base(commitURL)+"/statuses") + reqOne := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/"+path.Base(commitURL)+"/status") + testRepoCommitsWithStatus(t, session.MakeRequest(t, req, http.StatusOK), session.MakeRequest(t, reqOne, http.StatusOK), state) + + // By Ref + req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/master/statuses") + reqOne = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/master/status") + testRepoCommitsWithStatus(t, session.MakeRequest(t, req, http.StatusOK), session.MakeRequest(t, reqOne, http.StatusOK), state) + req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/v1.1/statuses") + reqOne = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/v1.1/status") + testRepoCommitsWithStatus(t, session.MakeRequest(t, req, http.StatusOK), session.MakeRequest(t, reqOne, http.StatusOK), state) +} + +func testRepoCommitsWithStatus(t *testing.T, resp, respOne *httptest.ResponseRecorder, state string) { + var statuses []*api.CommitStatus + assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), &statuses)) + var status api.CombinedStatus + assert.NoError(t, json.Unmarshal(respOne.Body.Bytes(), &status)) + assert.NotNil(t, status) + + if assert.Len(t, statuses, 1) { + assert.Equal(t, api.CommitStatusState(state), statuses[0].State) + assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/statuses/65f1bf27bc3bf70f64657658635e66094edbcb4d", statuses[0].URL) + assert.Equal(t, "http://test.ci/", statuses[0].TargetURL) + assert.Equal(t, "", statuses[0].Description) + assert.Equal(t, "testci", statuses[0].Context) + + assert.Len(t, status.Statuses, 1) + assert.Equal(t, statuses[0], status.Statuses[0]) + assert.Equal(t, "65f1bf27bc3bf70f64657658635e66094edbcb4d", status.SHA) + } +} + +func TestRepoCommitsWithStatusPending(t *testing.T) { + doTestRepoCommitWithStatus(t, "pending", "octicon-dot-fill", "yellow") +} + +func TestRepoCommitsWithStatusSuccess(t *testing.T) { + doTestRepoCommitWithStatus(t, "success", "octicon-check", "green") +} + +func TestRepoCommitsWithStatusError(t *testing.T) { + doTestRepoCommitWithStatus(t, "error", "gitea-exclamation", "red") +} + +func TestRepoCommitsWithStatusFailure(t *testing.T) { + doTestRepoCommitWithStatus(t, "failure", "octicon-x", "red") +} + +func TestRepoCommitsWithStatusWarning(t *testing.T) { + doTestRepoCommitWithStatus(t, "warning", "gitea-exclamation", "yellow") +} diff --git a/tests/integration/repo_fork_test.go b/tests/integration/repo_fork_test.go new file mode 100644 index 0000000000..4ab3577b54 --- /dev/null +++ b/tests/integration/repo_fork_test.go @@ -0,0 +1,76 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkOwnerName, forkRepoName string) *httptest.ResponseRecorder { + forkOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: forkOwnerName}) + + // Step0: check the existence of the to-fork repo + req := NewRequestf(t, "GET", "/%s/%s", forkOwnerName, forkRepoName) + session.MakeRequest(t, req, http.StatusNotFound) + + // Step1: go to the main page of repo + req = NewRequestf(t, "GET", "/%s/%s", ownerName, repoName) + resp := session.MakeRequest(t, req, http.StatusOK) + + // Step2: click the fork button + htmlDoc := NewHTMLParser(t, resp.Body) + link, exists := htmlDoc.doc.Find("a.ui.button[href^=\"/repo/fork/\"]").Attr("href") + assert.True(t, exists, "The template has changed") + req = NewRequest(t, "GET", link) + resp = session.MakeRequest(t, req, http.StatusOK) + + // Step3: fill the form of the forking + htmlDoc = NewHTMLParser(t, resp.Body) + link, exists = htmlDoc.doc.Find("form.ui.form[action^=\"/repo/fork/\"]").Attr("action") + assert.True(t, exists, "The template has changed") + _, exists = htmlDoc.doc.Find(fmt.Sprintf(".owner.dropdown .item[data-value=\"%d\"]", forkOwner.ID)).Attr("data-value") + assert.True(t, exists, fmt.Sprintf("Fork owner '%s' is not present in select box", forkOwnerName)) + req = NewRequestWithValues(t, "POST", link, map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "uid": fmt.Sprintf("%d", forkOwner.ID), + "repo_name": forkRepoName, + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // Step4: check the existence of the forked repo + req = NewRequestf(t, "GET", "/%s/%s", forkOwnerName, forkRepoName) + resp = session.MakeRequest(t, req, http.StatusOK) + + return resp +} + +func TestRepoFork(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") +} + +func TestRepoForkToOrg(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + testRepoFork(t, session, "user2", "repo1", "user3", "repo1") + + // Check that no more forking is allowed as user2 owns repository + // and user3 organization that owner user2 is also now has forked this repository + req := NewRequest(t, "GET", "/user2/repo1") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + _, exists := htmlDoc.doc.Find("a.ui.button[href^=\"/repo/fork/\"]").Attr("href") + assert.False(t, exists, "Forking should not be allowed anymore") +} diff --git a/tests/integration/repo_generate_test.go b/tests/integration/repo_generate_test.go new file mode 100644 index 0000000000..61a632721e --- /dev/null +++ b/tests/integration/repo_generate_test.go @@ -0,0 +1,69 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func testRepoGenerate(t *testing.T, session *TestSession, templateOwnerName, templateRepoName, generateOwnerName, generateRepoName string) *httptest.ResponseRecorder { + generateOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: generateOwnerName}) + + // Step0: check the existence of the generated repo + req := NewRequestf(t, "GET", "/%s/%s", generateOwnerName, generateRepoName) + session.MakeRequest(t, req, http.StatusNotFound) + + // Step1: go to the main page of template repo + req = NewRequestf(t, "GET", "/%s/%s", templateOwnerName, templateRepoName) + resp := session.MakeRequest(t, req, http.StatusOK) + + // Step2: click the "Use this template" button + htmlDoc := NewHTMLParser(t, resp.Body) + link, exists := htmlDoc.doc.Find("a.ui.button[href^=\"/repo/create\"]").Attr("href") + assert.True(t, exists, "The template has changed") + req = NewRequest(t, "GET", link) + resp = session.MakeRequest(t, req, http.StatusOK) + + // Step3: fill the form of the create + htmlDoc = NewHTMLParser(t, resp.Body) + link, exists = htmlDoc.doc.Find("form.ui.form[action^=\"/repo/create\"]").Attr("action") + assert.True(t, exists, "The template has changed") + _, exists = htmlDoc.doc.Find(fmt.Sprintf(".owner.dropdown .item[data-value=\"%d\"]", generateOwner.ID)).Attr("data-value") + assert.True(t, exists, fmt.Sprintf("Generate owner '%s' is not present in select box", generateOwnerName)) + req = NewRequestWithValues(t, "POST", link, map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "uid": fmt.Sprintf("%d", generateOwner.ID), + "repo_name": generateRepoName, + "git_content": "true", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // Step4: check the existence of the generated repo + req = NewRequestf(t, "GET", "/%s/%s", generateOwnerName, generateRepoName) + resp = session.MakeRequest(t, req, http.StatusOK) + + return resp +} + +func TestRepoGenerate(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user1") + testRepoGenerate(t, session, "user27", "template1", "user1", "generated1") +} + +func TestRepoGenerateToOrg(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + testRepoGenerate(t, session, "user27", "template1", "user2", "generated2") +} diff --git a/tests/integration/repo_migrate_test.go b/tests/integration/repo_migrate_test.go new file mode 100644 index 0000000000..c69a2642cb --- /dev/null +++ b/tests/integration/repo_migrate_test.go @@ -0,0 +1,46 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func testRepoMigrate(t testing.TB, session *TestSession, cloneAddr, repoName string) *httptest.ResponseRecorder { + req := NewRequest(t, "GET", fmt.Sprintf("/repo/migrate?service_type=%d", structs.PlainGitService)) // render plain git migration page + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + link, exists := htmlDoc.doc.Find("form.ui.form").Attr("action") + assert.True(t, exists, "The template has changed") + + uid, exists := htmlDoc.doc.Find("#uid").Attr("value") + assert.True(t, exists, "The template has changed") + + req = NewRequestWithValues(t, "POST", link, map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "clone_addr": cloneAddr, + "uid": uid, + "repo_name": repoName, + "service": fmt.Sprintf("%d", structs.PlainGitService), + }) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + + return resp +} + +func TestRepoMigrate(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + testRepoMigrate(t, session, "https://github.com/go-gitea/test_repo.git", "git") +} diff --git a/tests/integration/repo_search_test.go b/tests/integration/repo_search_test.go new file mode 100644 index 0000000000..b20943c22a --- /dev/null +++ b/tests/integration/repo_search_test.go @@ -0,0 +1,63 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + code_indexer "code.gitea.io/gitea/modules/indexer/code" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/tests" + + "github.com/PuerkitoBio/goquery" + "github.com/stretchr/testify/assert" +) + +func resultFilenames(t testing.TB, doc *HTMLDoc) []string { + filenameSelections := doc.doc.Find(".repository.search").Find(".repo-search-result").Find(".header").Find("span.file") + result := make([]string, filenameSelections.Length()) + filenameSelections.Each(func(i int, selection *goquery.Selection) { + result[i] = selection.Text() + }) + return result +} + +func TestSearchRepo(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo, err := repo_model.GetRepositoryByOwnerAndName("user2", "repo1") + assert.NoError(t, err) + + executeIndexer(t, repo, code_indexer.UpdateRepoIndexer) + + testSearch(t, "/user2/repo1/search?q=Description&page=1", []string{"README.md"}) + + setting.Indexer.IncludePatterns = setting.IndexerGlobFromString("**.txt") + setting.Indexer.ExcludePatterns = setting.IndexerGlobFromString("**/y/**") + + repo, err = repo_model.GetRepositoryByOwnerAndName("user2", "glob") + assert.NoError(t, err) + + executeIndexer(t, repo, code_indexer.UpdateRepoIndexer) + + testSearch(t, "/user2/glob/search?q=loren&page=1", []string{"a.txt"}) + testSearch(t, "/user2/glob/search?q=file3&page=1", []string{"x/b.txt"}) + testSearch(t, "/user2/glob/search?q=file4&page=1", []string{}) + testSearch(t, "/user2/glob/search?q=file5&page=1", []string{}) +} + +func testSearch(t *testing.T, url string, expected []string) { + req := NewRequestf(t, "GET", url) + resp := MakeRequest(t, req, http.StatusOK) + + filenames := resultFilenames(t, NewHTMLParser(t, resp.Body)) + assert.EqualValues(t, expected, filenames) +} + +func executeIndexer(t *testing.T, repo *repo_model.Repository, op func(*repo_model.Repository)) { + op(repo) +} diff --git a/tests/integration/repo_tag_test.go b/tests/integration/repo_tag_test.go new file mode 100644 index 0000000000..a91f1fb209 --- /dev/null +++ b/tests/integration/repo_tag_test.go @@ -0,0 +1,99 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/url" + "os" + "testing" + + "code.gitea.io/gitea/models" + git_model "code.gitea.io/gitea/models/git" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/release" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestCreateNewTagProtected(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + t.Run("API", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + err := release.CreateNewTag(git.DefaultContext, owner, repo, "master", "v-1", "first tag") + assert.NoError(t, err) + + err = git_model.InsertProtectedTag(&git_model.ProtectedTag{ + RepoID: repo.ID, + NamePattern: "v-*", + }) + assert.NoError(t, err) + err = git_model.InsertProtectedTag(&git_model.ProtectedTag{ + RepoID: repo.ID, + NamePattern: "v-1.1", + AllowlistUserIDs: []int64{repo.OwnerID}, + }) + assert.NoError(t, err) + + err = release.CreateNewTag(git.DefaultContext, owner, repo, "master", "v-2", "second tag") + assert.Error(t, err) + assert.True(t, models.IsErrProtectedTagName(err)) + + err = release.CreateNewTag(git.DefaultContext, owner, repo, "master", "v-1.1", "third tag") + assert.NoError(t, err) + }) + + t.Run("Git", func(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + username := "user2" + httpContext := NewAPITestContext(t, username, "repo1") + + dstPath, err := os.MkdirTemp("", httpContext.Reponame) + assert.NoError(t, err) + defer util.RemoveAll(dstPath) + + u.Path = httpContext.GitPath() + u.User = url.UserPassword(username, userPassword) + + doGitClone(dstPath, u)(t) + + _, _, err = git.NewCommand(git.DefaultContext, "tag", "v-2").RunStdString(&git.RunOpts{Dir: dstPath}) + assert.NoError(t, err) + + _, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Tag v-2 is protected") + }) + }) + + // Cleanup + releases, err := repo_model.GetReleasesByRepoID(repo.ID, repo_model.FindReleasesOptions{ + IncludeTags: true, + TagNames: []string{"v-1", "v-1.1"}, + }) + assert.NoError(t, err) + + for _, release := range releases { + err = repo_model.DeleteReleaseByID(release.ID) + assert.NoError(t, err) + } + + protectedTags, err := git_model.GetProtectedTags(repo.ID) + assert.NoError(t, err) + + for _, protectedTag := range protectedTags { + err = git_model.DeleteProtectedTag(protectedTag) + assert.NoError(t, err) + } +} diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go new file mode 100644 index 0000000000..8dfa9d08f1 --- /dev/null +++ b/tests/integration/repo_test.go @@ -0,0 +1,184 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "path" + "strings" + "testing" + "time" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/tests" + + "github.com/PuerkitoBio/goquery" + "github.com/stretchr/testify/assert" +) + +func TestViewRepo(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/user2/repo1") + MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", "/user3/repo3") + MakeRequest(t, req, http.StatusNotFound) + + session := loginUser(t, "user1") + session.MakeRequest(t, req, http.StatusNotFound) +} + +func testViewRepo(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/user3/repo3") + session := loginUser(t, "user2") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + files := htmlDoc.doc.Find("#repo-files-table > TBODY > TR") + + type file struct { + fileName string + commitID string + commitMsg string + commitTime string + } + + var items []file + + files.Each(func(i int, s *goquery.Selection) { + tds := s.Find("td") + var f file + tds.Each(func(i int, s *goquery.Selection) { + if i == 0 { + f.fileName = strings.TrimSpace(s.Text()) + } else if i == 1 { + a := s.Find("a") + f.commitMsg = strings.TrimSpace(a.Text()) + l, _ := a.Attr("href") + f.commitID = path.Base(l) + } + }) + + f.commitTime, _ = s.Find("span.time-since").Attr("data-content") + items = append(items, f) + }) + + commitT := time.Date(2017, time.June, 14, 13, 54, 21, 0, time.UTC).In(time.Local).Format(time.RFC1123) + assert.EqualValues(t, []file{ + { + fileName: "doc", + commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6", + commitMsg: "init project", + commitTime: commitT, + }, + { + fileName: "README.md", + commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6", + commitMsg: "init project", + commitTime: commitT, + }, + }, items) +} + +func TestViewRepo2(t *testing.T) { + // no last commit cache + testViewRepo(t) + + // enable last commit cache for all repositories + oldCommitsCount := setting.CacheService.LastCommit.CommitsCount + setting.CacheService.LastCommit.CommitsCount = 0 + // first view will not hit the cache + testViewRepo(t) + // second view will hit the cache + testViewRepo(t) + setting.CacheService.LastCommit.CommitsCount = oldCommitsCount +} + +func TestViewRepo3(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/user3/repo3") + session := loginUser(t, "user4") + session.MakeRequest(t, req, http.StatusOK) +} + +func TestViewRepo1CloneLinkAnonymous(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/user2/repo1") + resp := MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + link, exists := htmlDoc.doc.Find("#repo-clone-https").Attr("data-link") + assert.True(t, exists, "The template has changed") + assert.Equal(t, setting.AppURL+"user2/repo1.git", link) + _, exists = htmlDoc.doc.Find("#repo-clone-ssh").Attr("data-link") + assert.False(t, exists) +} + +func TestViewRepo1CloneLinkAuthorized(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + + req := NewRequest(t, "GET", "/user2/repo1") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + link, exists := htmlDoc.doc.Find("#repo-clone-https").Attr("data-link") + assert.True(t, exists, "The template has changed") + assert.Equal(t, setting.AppURL+"user2/repo1.git", link) + link, exists = htmlDoc.doc.Find("#repo-clone-ssh").Attr("data-link") + assert.True(t, exists, "The template has changed") + sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port) + assert.Equal(t, sshURL, link) +} + +func TestViewRepoWithSymlinks(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + + req := NewRequest(t, "GET", "/user2/repo20.git") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + files := htmlDoc.doc.Find("#repo-files-table > TBODY > TR > TD.name > SPAN.truncate") + items := files.Map(func(i int, s *goquery.Selection) string { + cls, _ := s.Find("SVG").Attr("class") + file := strings.Trim(s.Find("A").Text(), " \t\n") + return fmt.Sprintf("%s: %s", file, cls) + }) + assert.Len(t, items, 5) + assert.Equal(t, "a: svg octicon-file-directory-fill", items[0]) + assert.Equal(t, "link_b: svg octicon-file-submodule", items[1]) + assert.Equal(t, "link_d: svg octicon-file-symlink-file", items[2]) + assert.Equal(t, "link_hi: svg octicon-file-symlink-file", items[3]) + assert.Equal(t, "link_link: svg octicon-file-symlink-file", items[4]) +} + +// TestViewAsRepoAdmin tests PR #2167 +func TestViewAsRepoAdmin(t *testing.T) { + for user, expectedNoDescription := range map[string]bool{ + "user2": true, + "user4": false, + } { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, user) + + req := NewRequest(t, "GET", "/user2/repo1.git") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + noDescription := htmlDoc.doc.Find("#repo-desc").Children() + + assert.Equal(t, expectedNoDescription, noDescription.HasClass("no-description")) + } +} diff --git a/tests/integration/repo_topic_test.go b/tests/integration/repo_topic_test.go new file mode 100644 index 0000000000..5ff0c8273a --- /dev/null +++ b/tests/integration/repo_topic_test.go @@ -0,0 +1,48 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "net/url" + "testing" + + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestTopicSearch(t *testing.T) { + defer tests.PrepareTestEnv(t)() + searchURL, _ := url.Parse("/explore/topics/search") + var topics struct { + TopicNames []*api.TopicResponse `json:"topics"` + } + + query := url.Values{"page": []string{"1"}, "limit": []string{"4"}} + + searchURL.RawQuery = query.Encode() + res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) + DecodeJSON(t, res, &topics) + assert.Len(t, topics.TopicNames, 4) + assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + + query.Add("q", "topic") + searchURL.RawQuery = query.Encode() + res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) + DecodeJSON(t, res, &topics) + assert.Len(t, topics.TopicNames, 2) + + query.Set("q", "database") + searchURL.RawQuery = query.Encode() + res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) + DecodeJSON(t, res, &topics) + if assert.Len(t, topics.TopicNames, 1) { + assert.EqualValues(t, 2, topics.TopicNames[0].ID) + assert.EqualValues(t, "database", topics.TopicNames[0].Name) + assert.EqualValues(t, 1, topics.TopicNames[0].RepoCount) + } +} diff --git a/tests/integration/repo_watch_test.go b/tests/integration/repo_watch_test.go new file mode 100644 index 0000000000..152600bf29 --- /dev/null +++ b/tests/integration/repo_watch_test.go @@ -0,0 +1,25 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/url" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/setting" +) + +func TestRepoWatch(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // Test round-trip auto-watch + setting.Service.AutoWatchOnChanges = true + session := loginUser(t, "user2") + unittest.AssertNotExistsBean(t, &repo_model.Watch{UserID: 2, RepoID: 3}) + testEditFile(t, session, "user3", "repo3", "master", "README.md", "Hello, World (Edited for watch)\n") + unittest.AssertExistsAndLoadBean(t, &repo_model.Watch{UserID: 2, RepoID: 3, Mode: repo_model.WatchModeAuto}) + }) +} diff --git a/tests/integration/repofiles_delete_test.go b/tests/integration/repofiles_delete_test.go new file mode 100644 index 0000000000..f594efdeeb --- /dev/null +++ b/tests/integration/repofiles_delete_test.go @@ -0,0 +1,202 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/url" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/git" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" + files_service "code.gitea.io/gitea/services/repository/files" + + "github.com/stretchr/testify/assert" +) + +func getDeleteRepoFileOptions(repo *repo_model.Repository) *files_service.DeleteRepoFileOptions { + return &files_service.DeleteRepoFileOptions{ + LastCommitID: "", + OldBranch: repo.DefaultBranch, + NewBranch: repo.DefaultBranch, + TreePath: "README.md", + Message: "Deletes README.md", + SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", + Author: &files_service.IdentityOptions{ + Name: "Bob Smith", + Email: "bob@smith.com", + }, + Committer: nil, + } +} + +func getExpectedDeleteFileResponse(u *url.URL) *api.FileResponse { + // Just returns fields that don't change, i.e. fields with commit SHAs and dates can't be determined + return &api.FileResponse{ + Content: nil, + Commit: &api.FileCommitResponse{ + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Bob Smith", + Email: "bob@smith.com", + }, + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "Bob Smith", + Email: "bob@smith.com", + }, + }, + Message: "Deletes README.md\n", + }, + Verification: &api.PayloadCommitVerification{ + Verified: false, + Reason: "gpg.error.not_signed_commit", + Signature: "", + Payload: "", + }, + } +} + +func TestDeleteRepoFile(t *testing.T) { + onGiteaRun(t, testDeleteRepoFile) +} + +func testDeleteRepoFile(t *testing.T, u *url.URL) { + // setup + unittest.PrepareTestEnv(t) + ctx := test.MockContext(t, "user2/repo1") + ctx.SetParams(":id", "1") + test.LoadRepo(t, ctx, 1) + test.LoadRepoCommit(t, ctx) + test.LoadUser(t, ctx, 2) + test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + repo := ctx.Repo.Repository + doer := ctx.Doer + opts := getDeleteRepoFileOptions(repo) + + t.Run("Delete README.md file", func(t *testing.T) { + fileResponse, err := files_service.DeleteRepoFile(git.DefaultContext, repo, doer, opts) + assert.NoError(t, err) + expectedFileResponse := getExpectedDeleteFileResponse(u) + assert.NotNil(t, fileResponse) + assert.Nil(t, fileResponse.Content) + assert.EqualValues(t, expectedFileResponse.Commit.Message, fileResponse.Commit.Message) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Identity, fileResponse.Commit.Author.Identity) + assert.EqualValues(t, expectedFileResponse.Commit.Committer.Identity, fileResponse.Commit.Committer.Identity) + assert.EqualValues(t, expectedFileResponse.Verification, fileResponse.Verification) + }) + + t.Run("Verify README.md has been deleted", func(t *testing.T) { + fileResponse, err := files_service.DeleteRepoFile(git.DefaultContext, repo, doer, opts) + assert.Nil(t, fileResponse) + expectedError := "repository file does not exist [path: " + opts.TreePath + "]" + assert.EqualError(t, err, expectedError) + }) +} + +// Test opts with branch names removed, same results +func TestDeleteRepoFileWithoutBranchNames(t *testing.T) { + onGiteaRun(t, testDeleteRepoFileWithoutBranchNames) +} + +func testDeleteRepoFileWithoutBranchNames(t *testing.T, u *url.URL) { + // setup + unittest.PrepareTestEnv(t) + ctx := test.MockContext(t, "user2/repo1") + ctx.SetParams(":id", "1") + test.LoadRepo(t, ctx, 1) + test.LoadRepoCommit(t, ctx) + test.LoadUser(t, ctx, 2) + test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + + repo := ctx.Repo.Repository + doer := ctx.Doer + opts := getDeleteRepoFileOptions(repo) + opts.OldBranch = "" + opts.NewBranch = "" + + t.Run("Delete README.md without Branch Name", func(t *testing.T) { + fileResponse, err := files_service.DeleteRepoFile(git.DefaultContext, repo, doer, opts) + assert.NoError(t, err) + expectedFileResponse := getExpectedDeleteFileResponse(u) + assert.NotNil(t, fileResponse) + assert.Nil(t, fileResponse.Content) + assert.EqualValues(t, expectedFileResponse.Commit.Message, fileResponse.Commit.Message) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Identity, fileResponse.Commit.Author.Identity) + assert.EqualValues(t, expectedFileResponse.Commit.Committer.Identity, fileResponse.Commit.Committer.Identity) + assert.EqualValues(t, expectedFileResponse.Verification, fileResponse.Verification) + }) +} + +func TestDeleteRepoFileErrors(t *testing.T) { + // setup + unittest.PrepareTestEnv(t) + ctx := test.MockContext(t, "user2/repo1") + ctx.SetParams(":id", "1") + test.LoadRepo(t, ctx, 1) + test.LoadRepoCommit(t, ctx) + test.LoadUser(t, ctx, 2) + test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + + repo := ctx.Repo.Repository + doer := ctx.Doer + + t.Run("Bad branch", func(t *testing.T) { + opts := getDeleteRepoFileOptions(repo) + opts.OldBranch = "bad_branch" + fileResponse, err := files_service.DeleteRepoFile(git.DefaultContext, repo, doer, opts) + assert.Error(t, err) + assert.Nil(t, fileResponse) + expectedError := "branch does not exist [name: " + opts.OldBranch + "]" + assert.EqualError(t, err, expectedError) + }) + + t.Run("Bad SHA", func(t *testing.T) { + opts := getDeleteRepoFileOptions(repo) + origSHA := opts.SHA + opts.SHA = "bad_sha" + fileResponse, err := files_service.DeleteRepoFile(git.DefaultContext, repo, doer, opts) + assert.Nil(t, fileResponse) + assert.Error(t, err) + expectedError := "sha does not match [given: " + opts.SHA + ", expected: " + origSHA + "]" + assert.EqualError(t, err, expectedError) + }) + + t.Run("New branch already exists", func(t *testing.T) { + opts := getDeleteRepoFileOptions(repo) + opts.NewBranch = "develop" + fileResponse, err := files_service.DeleteRepoFile(git.DefaultContext, repo, doer, opts) + assert.Nil(t, fileResponse) + assert.Error(t, err) + expectedError := "branch already exists [name: " + opts.NewBranch + "]" + assert.EqualError(t, err, expectedError) + }) + + t.Run("TreePath is empty:", func(t *testing.T) { + opts := getDeleteRepoFileOptions(repo) + opts.TreePath = "" + fileResponse, err := files_service.DeleteRepoFile(git.DefaultContext, repo, doer, opts) + assert.Nil(t, fileResponse) + assert.Error(t, err) + expectedError := "path contains a malformed path component [path: ]" + assert.EqualError(t, err, expectedError) + }) + + t.Run("TreePath is a git directory:", func(t *testing.T) { + opts := getDeleteRepoFileOptions(repo) + opts.TreePath = ".git" + fileResponse, err := files_service.DeleteRepoFile(git.DefaultContext, repo, doer, opts) + assert.Nil(t, fileResponse) + assert.Error(t, err) + expectedError := "path contains a malformed path component [path: " + opts.TreePath + "]" + assert.EqualError(t, err, expectedError) + }) +} diff --git a/tests/integration/repofiles_update_test.go b/tests/integration/repofiles_update_test.go new file mode 100644 index 0000000000..c62c49eeeb --- /dev/null +++ b/tests/integration/repofiles_update_test.go @@ -0,0 +1,416 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/url" + "path/filepath" + "testing" + "time" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" + files_service "code.gitea.io/gitea/services/repository/files" + + "github.com/stretchr/testify/assert" +) + +func getCreateRepoFileOptions(repo *repo_model.Repository) *files_service.UpdateRepoFileOptions { + return &files_service.UpdateRepoFileOptions{ + OldBranch: repo.DefaultBranch, + NewBranch: repo.DefaultBranch, + TreePath: "new/file.txt", + Message: "Creates new/file.txt", + Content: "This is a NEW file", + IsNewFile: true, + Author: nil, + Committer: nil, + } +} + +func getUpdateRepoFileOptions(repo *repo_model.Repository) *files_service.UpdateRepoFileOptions { + return &files_service.UpdateRepoFileOptions{ + OldBranch: repo.DefaultBranch, + NewBranch: repo.DefaultBranch, + TreePath: "README.md", + Message: "Updates README.md", + SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", + Content: "This is UPDATED content for the README file", + IsNewFile: false, + Author: nil, + Committer: nil, + } +} + +func getExpectedFileResponseForRepofilesCreate(commitID, lastCommitSHA string) *api.FileResponse { + treePath := "new/file.txt" + encoding := "base64" + content := "VGhpcyBpcyBhIE5FVyBmaWxl" + selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master" + htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath + gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/103ff9234cefeee5ec5361d22b49fbb04d385885" + downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath + return &api.FileResponse{ + Content: &api.ContentsResponse{ + Name: filepath.Base(treePath), + Path: treePath, + SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", + LastCommitSHA: lastCommitSHA, + Type: "file", + Size: 18, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, + Links: &api.FileLinksResponse{ + Self: &selfURL, + GitURL: &gitURL, + HTMLURL: &htmlURL, + }, + }, + Commit: &api.FileCommitResponse{ + CommitMeta: api.CommitMeta{ + URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + commitID, + SHA: commitID, + }, + HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID, + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "User Two", + Email: "user2@noreply.example.org", + }, + Date: time.Now().UTC().Format(time.RFC3339), + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "User Two", + Email: "user2@noreply.example.org", + }, + Date: time.Now().UTC().Format(time.RFC3339), + }, + Parents: []*api.CommitMeta{ + { + URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", + SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", + }, + }, + Message: "Updates README.md\n", + Tree: &api.CommitMeta{ + URL: setting.AppURL + "api/v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc", + SHA: "f93e3a1a1525fb5b91020git dda86e44810c87a2d7bc", + }, + }, + Verification: &api.PayloadCommitVerification{ + Verified: false, + Reason: "gpg.error.not_signed_commit", + Signature: "", + Payload: "", + }, + } +} + +func getExpectedFileResponseForRepofilesUpdate(commitID, filename, lastCommitSHA string) *api.FileResponse { + encoding := "base64" + content := "VGhpcyBpcyBVUERBVEVEIGNvbnRlbnQgZm9yIHRoZSBSRUFETUUgZmlsZQ==" + selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + filename + "?ref=master" + htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + filename + gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/dbf8d00e022e05b7e5cf7e535de857de57925647" + downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + filename + return &api.FileResponse{ + Content: &api.ContentsResponse{ + Name: filename, + Path: filename, + SHA: "dbf8d00e022e05b7e5cf7e535de857de57925647", + LastCommitSHA: lastCommitSHA, + Type: "file", + Size: 43, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, + Links: &api.FileLinksResponse{ + Self: &selfURL, + GitURL: &gitURL, + HTMLURL: &htmlURL, + }, + }, + Commit: &api.FileCommitResponse{ + CommitMeta: api.CommitMeta{ + URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + commitID, + SHA: commitID, + }, + HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID, + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "User Two", + Email: "user2@noreply.example.org", + }, + Date: time.Now().UTC().Format(time.RFC3339), + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "User Two", + Email: "user2@noreply.example.org", + }, + Date: time.Now().UTC().Format(time.RFC3339), + }, + Parents: []*api.CommitMeta{ + { + URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", + SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", + }, + }, + Message: "Updates README.md\n", + Tree: &api.CommitMeta{ + URL: setting.AppURL + "api/v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc", + SHA: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc", + }, + }, + Verification: &api.PayloadCommitVerification{ + Verified: false, + Reason: "gpg.error.not_signed_commit", + Signature: "", + Payload: "", + }, + } +} + +func TestCreateOrUpdateRepoFileForCreate(t *testing.T) { + // setup + onGiteaRun(t, func(t *testing.T, u *url.URL) { + ctx := test.MockContext(t, "user2/repo1") + ctx.SetParams(":id", "1") + test.LoadRepo(t, ctx, 1) + test.LoadRepoCommit(t, ctx) + test.LoadUser(t, ctx, 2) + test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + + repo := ctx.Repo.Repository + doer := ctx.Doer + opts := getCreateRepoFileOptions(repo) + + // test + fileResponse, err := files_service.CreateOrUpdateRepoFile(git.DefaultContext, repo, doer, opts) + + // asserts + assert.NoError(t, err) + gitRepo, _ := git.OpenRepository(git.DefaultContext, repo.RepoPath()) + defer gitRepo.Close() + + commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch) + lastCommit, _ := gitRepo.GetCommitByPath("new/file.txt") + expectedFileResponse := getExpectedFileResponseForRepofilesCreate(commitID, lastCommit.ID.String()) + assert.NotNil(t, expectedFileResponse) + if expectedFileResponse != nil { + assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) + assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + } + }) +} + +func TestCreateOrUpdateRepoFileForUpdate(t *testing.T) { + // setup + onGiteaRun(t, func(t *testing.T, u *url.URL) { + ctx := test.MockContext(t, "user2/repo1") + ctx.SetParams(":id", "1") + test.LoadRepo(t, ctx, 1) + test.LoadRepoCommit(t, ctx) + test.LoadUser(t, ctx, 2) + test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + + repo := ctx.Repo.Repository + doer := ctx.Doer + opts := getUpdateRepoFileOptions(repo) + + // test + fileResponse, err := files_service.CreateOrUpdateRepoFile(git.DefaultContext, repo, doer, opts) + + // asserts + assert.NoError(t, err) + gitRepo, _ := git.OpenRepository(git.DefaultContext, repo.RepoPath()) + defer gitRepo.Close() + + commit, _ := gitRepo.GetBranchCommit(opts.NewBranch) + lastCommit, _ := commit.GetCommitByPath(opts.TreePath) + expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.TreePath, lastCommit.ID.String()) + assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) + assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + }) +} + +func TestCreateOrUpdateRepoFileForUpdateWithFileMove(t *testing.T) { + // setup + onGiteaRun(t, func(t *testing.T, u *url.URL) { + ctx := test.MockContext(t, "user2/repo1") + ctx.SetParams(":id", "1") + test.LoadRepo(t, ctx, 1) + test.LoadRepoCommit(t, ctx) + test.LoadUser(t, ctx, 2) + test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + + repo := ctx.Repo.Repository + doer := ctx.Doer + opts := getUpdateRepoFileOptions(repo) + opts.FromTreePath = "README.md" + opts.TreePath = "README_new.md" // new file name, README_new.md + + // test + fileResponse, err := files_service.CreateOrUpdateRepoFile(git.DefaultContext, repo, doer, opts) + + // asserts + assert.NoError(t, err) + gitRepo, _ := git.OpenRepository(git.DefaultContext, repo.RepoPath()) + defer gitRepo.Close() + + commit, _ := gitRepo.GetBranchCommit(opts.NewBranch) + lastCommit, _ := commit.GetCommitByPath(opts.TreePath) + expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.TreePath, lastCommit.ID.String()) + // assert that the old file no longer exists in the last commit of the branch + fromEntry, err := commit.GetTreeEntryByPath(opts.FromTreePath) + switch err.(type) { + case git.ErrNotExist: + // correct, continue + default: + t.Fatalf("expected git.ErrNotExist, got:%v", err) + } + toEntry, err := commit.GetTreeEntryByPath(opts.TreePath) + assert.NoError(t, err) + assert.Nil(t, fromEntry) // Should no longer exist here + assert.NotNil(t, toEntry) // Should exist here + // assert SHA has remained the same but paths use the new file name + assert.EqualValues(t, expectedFileResponse.Content.SHA, fileResponse.Content.SHA) + assert.EqualValues(t, expectedFileResponse.Content.Name, fileResponse.Content.Name) + assert.EqualValues(t, expectedFileResponse.Content.Path, fileResponse.Content.Path) + assert.EqualValues(t, expectedFileResponse.Content.URL, fileResponse.Content.URL) + assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + }) +} + +// Test opts with branch names removed, should get same results as above test +func TestCreateOrUpdateRepoFileWithoutBranchNames(t *testing.T) { + // setup + onGiteaRun(t, func(t *testing.T, u *url.URL) { + ctx := test.MockContext(t, "user2/repo1") + ctx.SetParams(":id", "1") + test.LoadRepo(t, ctx, 1) + test.LoadRepoCommit(t, ctx) + test.LoadUser(t, ctx, 2) + test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + + repo := ctx.Repo.Repository + doer := ctx.Doer + opts := getUpdateRepoFileOptions(repo) + opts.OldBranch = "" + opts.NewBranch = "" + + // test + fileResponse, err := files_service.CreateOrUpdateRepoFile(git.DefaultContext, repo, doer, opts) + + // asserts + assert.NoError(t, err) + gitRepo, _ := git.OpenRepository(git.DefaultContext, repo.RepoPath()) + defer gitRepo.Close() + + commit, _ := gitRepo.GetBranchCommit(repo.DefaultBranch) + lastCommit, _ := commit.GetCommitByPath(opts.TreePath) + expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.TreePath, lastCommit.ID.String()) + assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) + }) +} + +func TestCreateOrUpdateRepoFileErrors(t *testing.T) { + // setup + onGiteaRun(t, func(t *testing.T, u *url.URL) { + ctx := test.MockContext(t, "user2/repo1") + ctx.SetParams(":id", "1") + test.LoadRepo(t, ctx, 1) + test.LoadRepoCommit(t, ctx) + test.LoadUser(t, ctx, 2) + test.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + + repo := ctx.Repo.Repository + doer := ctx.Doer + + t.Run("bad branch", func(t *testing.T) { + opts := getUpdateRepoFileOptions(repo) + opts.OldBranch = "bad_branch" + fileResponse, err := files_service.CreateOrUpdateRepoFile(git.DefaultContext, repo, doer, opts) + assert.Error(t, err) + assert.Nil(t, fileResponse) + expectedError := "branch does not exist [name: " + opts.OldBranch + "]" + assert.EqualError(t, err, expectedError) + }) + + t.Run("bad SHA", func(t *testing.T) { + opts := getUpdateRepoFileOptions(repo) + origSHA := opts.SHA + opts.SHA = "bad_sha" + fileResponse, err := files_service.CreateOrUpdateRepoFile(git.DefaultContext, repo, doer, opts) + assert.Nil(t, fileResponse) + assert.Error(t, err) + expectedError := "sha does not match [given: " + opts.SHA + ", expected: " + origSHA + "]" + assert.EqualError(t, err, expectedError) + }) + + t.Run("new branch already exists", func(t *testing.T) { + opts := getUpdateRepoFileOptions(repo) + opts.NewBranch = "develop" + fileResponse, err := files_service.CreateOrUpdateRepoFile(git.DefaultContext, repo, doer, opts) + assert.Nil(t, fileResponse) + assert.Error(t, err) + expectedError := "branch already exists [name: " + opts.NewBranch + "]" + assert.EqualError(t, err, expectedError) + }) + + t.Run("treePath is empty:", func(t *testing.T) { + opts := getUpdateRepoFileOptions(repo) + opts.TreePath = "" + fileResponse, err := files_service.CreateOrUpdateRepoFile(git.DefaultContext, repo, doer, opts) + assert.Nil(t, fileResponse) + assert.Error(t, err) + expectedError := "path contains a malformed path component [path: ]" + assert.EqualError(t, err, expectedError) + }) + + t.Run("treePath is a git directory:", func(t *testing.T) { + opts := getUpdateRepoFileOptions(repo) + opts.TreePath = ".git" + fileResponse, err := files_service.CreateOrUpdateRepoFile(git.DefaultContext, repo, doer, opts) + assert.Nil(t, fileResponse) + assert.Error(t, err) + expectedError := "path contains a malformed path component [path: " + opts.TreePath + "]" + assert.EqualError(t, err, expectedError) + }) + + t.Run("create file that already exists", func(t *testing.T) { + opts := getCreateRepoFileOptions(repo) + opts.TreePath = "README.md" // already exists + fileResponse, err := files_service.CreateOrUpdateRepoFile(git.DefaultContext, repo, doer, opts) + assert.Nil(t, fileResponse) + assert.Error(t, err) + expectedError := "repository file already exists [path: " + opts.TreePath + "]" + assert.EqualError(t, err, expectedError) + }) + }) +} diff --git a/tests/integration/setting_test.go b/tests/integration/setting_test.go new file mode 100644 index 0000000000..6273545c23 --- /dev/null +++ b/tests/integration/setting_test.go @@ -0,0 +1,108 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestSettingShowUserEmailExplore(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + showUserEmail := setting.UI.ShowUserEmail + setting.UI.ShowUserEmail = true + + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/explore/users") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + assert.Contains(t, + htmlDoc.doc.Find(".ui.user.list").Text(), + "user4@example.com", + ) + + setting.UI.ShowUserEmail = false + + req = NewRequest(t, "GET", "/explore/users") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + assert.NotContains(t, + htmlDoc.doc.Find(".ui.user.list").Text(), + "user4@example.com", + ) + + setting.UI.ShowUserEmail = showUserEmail +} + +func TestSettingShowUserEmailProfile(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + showUserEmail := setting.UI.ShowUserEmail + setting.UI.ShowUserEmail = true + + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + assert.Contains(t, + htmlDoc.doc.Find(".user.profile").Text(), + "user2@example.com", + ) + + setting.UI.ShowUserEmail = false + + req = NewRequest(t, "GET", "/user2") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + // Should contain since this user owns the profile page + assert.Contains(t, + htmlDoc.doc.Find(".user.profile").Text(), + "user2@example.com", + ) + + setting.UI.ShowUserEmail = showUserEmail + + session = loginUser(t, "user4") + req = NewRequest(t, "GET", "/user2") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + assert.NotContains(t, + htmlDoc.doc.Find(".user.profile").Text(), + "user2@example.com", + ) +} + +func TestSettingLandingPage(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + landingPage := setting.LandingPageURL + + setting.LandingPageURL = setting.LandingPageHome + req := NewRequest(t, "GET", "/") + MakeRequest(t, req, http.StatusOK) + + setting.LandingPageURL = setting.LandingPageExplore + req = NewRequest(t, "GET", "/") + resp := MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/explore", resp.Header().Get("Location")) + + setting.LandingPageURL = setting.LandingPageOrganizations + req = NewRequest(t, "GET", "/") + resp = MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/explore/organizations", resp.Header().Get("Location")) + + setting.LandingPageURL = setting.LandingPageLogin + req = NewRequest(t, "GET", "/") + resp = MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/user/login", resp.Header().Get("Location")) + + setting.LandingPageURL = landingPage +} diff --git a/tests/integration/signin_test.go b/tests/integration/signin_test.go new file mode 100644 index 0000000000..7dc078e274 --- /dev/null +++ b/tests/integration/signin_test.go @@ -0,0 +1,60 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "strings" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func testLoginFailed(t *testing.T, username, password, message string) { + session := emptyTestSession(t) + req := NewRequestWithValues(t, "POST", "/user/login", map[string]string{ + "_csrf": GetCSRF(t, session, "/user/login"), + "user_name": username, + "password": password, + }) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + resultMsg := htmlDoc.doc.Find(".ui.message>p").Text() + + assert.EqualValues(t, message, resultMsg) +} + +func TestSignin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + // add new user with user2's email + user.Name = "testuser" + user.LowerName = strings.ToLower(user.Name) + user.ID = 0 + unittest.AssertSuccessfulInsert(t, user) + + samples := []struct { + username string + password string + message string + }{ + {username: "wrongUsername", password: "wrongPassword", message: translation.NewLocale("en-US").Tr("form.username_password_incorrect")}, + {username: "wrongUsername", password: "password", message: translation.NewLocale("en-US").Tr("form.username_password_incorrect")}, + {username: "user15", password: "wrongPassword", message: translation.NewLocale("en-US").Tr("form.username_password_incorrect")}, + {username: "user1@example.com", password: "wrongPassword", message: translation.NewLocale("en-US").Tr("form.username_password_incorrect")}, + } + + for _, s := range samples { + testLoginFailed(t, s.username, s.password, s.message) + } +} diff --git a/tests/integration/signout_test.go b/tests/integration/signout_test.go new file mode 100644 index 0000000000..1f1346a5c3 --- /dev/null +++ b/tests/integration/signout_test.go @@ -0,0 +1,28 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/tests" +) + +func TestSignOut(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + + req := NewRequest(t, "POST", "/user/logout") + session.MakeRequest(t, req, http.StatusSeeOther) + + // try to view a private repo, should fail + req = NewRequest(t, "GET", "/user2/repo2") + session.MakeRequest(t, req, http.StatusNotFound) + + // invalidate cached cookies for user2, for subsequent tests + delete(loginSessionCache, "user2") +} diff --git a/tests/integration/signup_test.go b/tests/integration/signup_test.go new file mode 100644 index 0000000000..1c598fd0d1 --- /dev/null +++ b/tests/integration/signup_test.go @@ -0,0 +1,94 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestSignup(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + setting.Service.EnableCaptcha = false + + req := NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{ + "user_name": "exampleUser", + "email": "exampleUser@example.com", + "password": "examplePassword!1", + "retype": "examplePassword!1", + }) + MakeRequest(t, req, http.StatusSeeOther) + + // should be able to view new user's page + req = NewRequest(t, "GET", "/exampleUser") + MakeRequest(t, req, http.StatusOK) +} + +func TestSignupAsRestricted(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + setting.Service.EnableCaptcha = false + setting.Service.DefaultUserIsRestricted = true + + req := NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{ + "user_name": "restrictedUser", + "email": "restrictedUser@example.com", + "password": "examplePassword!1", + "retype": "examplePassword!1", + }) + MakeRequest(t, req, http.StatusSeeOther) + + // should be able to view new user's page + req = NewRequest(t, "GET", "/restrictedUser") + MakeRequest(t, req, http.StatusOK) + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "restrictedUser"}) + assert.True(t, user2.IsRestricted) +} + +func TestSignupEmail(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + setting.Service.EnableCaptcha = false + + tests := []struct { + email string + wantStatus int + wantMsg string + }{ + {"exampleUser@example.com\r\n", http.StatusOK, translation.NewLocale("en-US").Tr("form.email_invalid")}, + {"exampleUser@example.com\r", http.StatusOK, translation.NewLocale("en-US").Tr("form.email_invalid")}, + {"exampleUser@example.com\n", http.StatusOK, translation.NewLocale("en-US").Tr("form.email_invalid")}, + {"exampleUser@example.com", http.StatusSeeOther, ""}, + } + + for i, test := range tests { + req := NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{ + "user_name": fmt.Sprintf("exampleUser%d", i), + "email": test.email, + "password": "examplePassword!1", + "retype": "examplePassword!1", + }) + resp := MakeRequest(t, req, test.wantStatus) + if test.wantMsg != "" { + htmlDoc := NewHTMLParser(t, resp.Body) + assert.Equal(t, + test.wantMsg, + strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()), + ) + } + } +} diff --git a/tests/integration/ssh_key_test.go b/tests/integration/ssh_key_test.go new file mode 100644 index 0000000000..65d9b84404 --- /dev/null +++ b/tests/integration/ssh_key_test.go @@ -0,0 +1,214 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "os" + "path/filepath" + "testing" + "time" + + "code.gitea.io/gitea/modules/git" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + + "github.com/stretchr/testify/assert" +) + +func doCheckRepositoryEmptyStatus(ctx APITestContext, isEmpty bool) func(*testing.T) { + return doAPIGetRepository(ctx, func(t *testing.T, repository api.Repository) { + assert.Equal(t, isEmpty, repository.Empty) + }) +} + +func doAddChangesToCheckout(dstPath, filename string) func(*testing.T) { + return func(t *testing.T) { + assert.NoError(t, os.WriteFile(filepath.Join(dstPath, filename), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s at time: %v", dstPath, time.Now())), 0o644)) + assert.NoError(t, git.AddChanges(dstPath, true)) + signature := git.Signature{ + Email: "test@example.com", + Name: "test", + When: time.Now(), + } + assert.NoError(t, git.CommitChanges(dstPath, git.CommitChangesOptions{ + Committer: &signature, + Author: &signature, + Message: "Initial Commit", + })) + } +} + +func TestPushDeployKeyOnEmptyRepo(t *testing.T) { + onGiteaRun(t, testPushDeployKeyOnEmptyRepo) +} + +func testPushDeployKeyOnEmptyRepo(t *testing.T, u *url.URL) { + // OK login + ctx := NewAPITestContext(t, "user2", "deploy-key-empty-repo-1") + keyname := fmt.Sprintf("%s-push", ctx.Reponame) + u.Path = ctx.GitPath() + + t.Run("CreateEmptyRepository", doAPICreateRepository(ctx, true)) + + t.Run("CheckIsEmpty", doCheckRepositoryEmptyStatus(ctx, true)) + + withKeyFile(t, keyname, func(keyFile string) { + t.Run("CreatePushDeployKey", doAPICreateDeployKey(ctx, keyname, keyFile, false)) + + // Setup the testing repository + dstPath, err := os.MkdirTemp("", "repo-tmp-deploy-key-empty-repo-1") + assert.NoError(t, err) + defer util.RemoveAll(dstPath) + + t.Run("InitTestRepository", doGitInitTestRepository(dstPath)) + + // Setup remote link + sshURL := createSSHUrl(ctx.GitPath(), u) + + t.Run("AddRemote", doGitAddRemote(dstPath, "origin", sshURL)) + + t.Run("SSHPushTestRepository", doGitPushTestRepository(dstPath, "origin", "master")) + + t.Run("CheckIsNotEmpty", doCheckRepositoryEmptyStatus(ctx, false)) + + t.Run("DeleteRepository", doAPIDeleteRepository(ctx)) + }) +} + +func TestKeyOnlyOneType(t *testing.T) { + onGiteaRun(t, testKeyOnlyOneType) +} + +func testKeyOnlyOneType(t *testing.T, u *url.URL) { + // Once a key is a user key we cannot use it as a deploy key + // If we delete it from the user we should be able to use it as a deploy key + reponame := "ssh-key-test-repo" + username := "user2" + u.Path = fmt.Sprintf("%s/%s.git", username, reponame) + keyname := fmt.Sprintf("%s-push", reponame) + + // OK login + ctx := NewAPITestContext(t, username, reponame) + + otherCtx := ctx + otherCtx.Reponame = "ssh-key-test-repo-2" + + failCtx := ctx + failCtx.ExpectedCode = http.StatusUnprocessableEntity + + t.Run("CreateRepository", doAPICreateRepository(ctx, false)) + t.Run("CreateOtherRepository", doAPICreateRepository(otherCtx, false)) + + withKeyFile(t, keyname, func(keyFile string) { + var userKeyPublicKeyID int64 + t.Run("KeyCanOnlyBeUser", func(t *testing.T) { + dstPath, err := os.MkdirTemp("", ctx.Reponame) + assert.NoError(t, err) + defer util.RemoveAll(dstPath) + + sshURL := createSSHUrl(ctx.GitPath(), u) + + t.Run("FailToClone", doGitCloneFail(sshURL)) + + t.Run("CreateUserKey", doAPICreateUserKey(ctx, keyname, keyFile, func(t *testing.T, publicKey api.PublicKey) { + userKeyPublicKeyID = publicKey.ID + })) + + t.Run("FailToAddReadOnlyDeployKey", doAPICreateDeployKey(failCtx, keyname, keyFile, true)) + + t.Run("FailToAddDeployKey", doAPICreateDeployKey(failCtx, keyname, keyFile, false)) + + t.Run("Clone", doGitClone(dstPath, sshURL)) + + t.Run("AddChanges", doAddChangesToCheckout(dstPath, "CHANGES1.md")) + + t.Run("Push", doGitPushTestRepository(dstPath, "origin", "master")) + + t.Run("DeleteUserKey", doAPIDeleteUserKey(ctx, userKeyPublicKeyID)) + }) + + t.Run("KeyCanBeAnyDeployButNotUserAswell", func(t *testing.T) { + dstPath, err := os.MkdirTemp("", ctx.Reponame) + assert.NoError(t, err) + defer util.RemoveAll(dstPath) + + sshURL := createSSHUrl(ctx.GitPath(), u) + + t.Run("FailToClone", doGitCloneFail(sshURL)) + + // Should now be able to add... + t.Run("AddReadOnlyDeployKey", doAPICreateDeployKey(ctx, keyname, keyFile, true)) + + t.Run("Clone", doGitClone(dstPath, sshURL)) + + t.Run("AddChanges", doAddChangesToCheckout(dstPath, "CHANGES2.md")) + + t.Run("FailToPush", doGitPushTestRepositoryFail(dstPath, "origin", "master")) + + otherSSHURL := createSSHUrl(otherCtx.GitPath(), u) + dstOtherPath, err := os.MkdirTemp("", otherCtx.Reponame) + assert.NoError(t, err) + defer util.RemoveAll(dstOtherPath) + + t.Run("AddWriterDeployKeyToOther", doAPICreateDeployKey(otherCtx, keyname, keyFile, false)) + + t.Run("CloneOther", doGitClone(dstOtherPath, otherSSHURL)) + + t.Run("AddChangesToOther", doAddChangesToCheckout(dstOtherPath, "CHANGES3.md")) + + t.Run("PushToOther", doGitPushTestRepository(dstOtherPath, "origin", "master")) + + t.Run("FailToCreateUserKey", doAPICreateUserKey(failCtx, keyname, keyFile)) + }) + + t.Run("DeleteRepositoryShouldReleaseKey", func(t *testing.T) { + otherSSHURL := createSSHUrl(otherCtx.GitPath(), u) + dstOtherPath, err := os.MkdirTemp("", otherCtx.Reponame) + assert.NoError(t, err) + defer util.RemoveAll(dstOtherPath) + + t.Run("DeleteRepository", doAPIDeleteRepository(ctx)) + + t.Run("FailToCreateUserKeyAsStillDeploy", doAPICreateUserKey(failCtx, keyname, keyFile)) + + t.Run("MakeSureCloneOtherStillWorks", doGitClone(dstOtherPath, otherSSHURL)) + + t.Run("AddChangesToOther", doAddChangesToCheckout(dstOtherPath, "CHANGES3.md")) + + t.Run("PushToOther", doGitPushTestRepository(dstOtherPath, "origin", "master")) + + t.Run("DeleteOtherRepository", doAPIDeleteRepository(otherCtx)) + + t.Run("RecreateRepository", doAPICreateRepository(ctx, false)) + + t.Run("CreateUserKey", doAPICreateUserKey(ctx, keyname, keyFile, func(t *testing.T, publicKey api.PublicKey) { + userKeyPublicKeyID = publicKey.ID + })) + + dstPath, err := os.MkdirTemp("", ctx.Reponame) + assert.NoError(t, err) + defer util.RemoveAll(dstPath) + + sshURL := createSSHUrl(ctx.GitPath(), u) + + t.Run("Clone", doGitClone(dstPath, sshURL)) + + t.Run("AddChanges", doAddChangesToCheckout(dstPath, "CHANGES1.md")) + + t.Run("Push", doGitPushTestRepository(dstPath, "origin", "master")) + }) + + t.Run("DeleteUserKeyShouldRemoveAbilityToClone", func(t *testing.T) { + sshURL := createSSHUrl(ctx.GitPath(), u) + + t.Run("DeleteUserKey", doAPIDeleteUserKey(ctx, userKeyPublicKeyID)) + + t.Run("FailToClone", doGitCloneFail(sshURL)) + }) + }) +} diff --git a/tests/integration/timetracking_test.go b/tests/integration/timetracking_test.go new file mode 100644 index 0000000000..54b81ff3bc --- /dev/null +++ b/tests/integration/timetracking_test.go @@ -0,0 +1,82 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "path" + "testing" + "time" + + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestViewTimetrackingControls(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + testViewTimetrackingControls(t, session, "user2", "repo1", "1", true) + // user2/repo1 +} + +func TestNotViewTimetrackingControls(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user5") + testViewTimetrackingControls(t, session, "user2", "repo1", "1", false) + // user2/repo1 +} + +func TestViewTimetrackingControlsDisabled(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + testViewTimetrackingControls(t, session, "user3", "repo3", "1", false) +} + +func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo, issue string, canTrackTime bool) { + req := NewRequest(t, "GET", path.Join(user, repo, "issues", issue)) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + htmlDoc.AssertElement(t, ".timetrack .issue-start-time", canTrackTime) + htmlDoc.AssertElement(t, ".timetrack .issue-add-time", canTrackTime) + + req = NewRequestWithValues(t, "POST", path.Join(user, repo, "issues", issue, "times", "stopwatch", "toggle"), map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + }) + if canTrackTime { + resp = session.MakeRequest(t, req, http.StatusSeeOther) + + req = NewRequest(t, "GET", test.RedirectURL(resp)) + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + + events := htmlDoc.doc.Find(".event > span.text") + assert.Contains(t, events.Last().Text(), "started working") + + htmlDoc.AssertElement(t, ".timetrack .issue-stop-time", true) + htmlDoc.AssertElement(t, ".timetrack .issue-cancel-time", true) + + // Sleep for 1 second to not get wrong order for stopping timer + time.Sleep(time.Second) + + req = NewRequestWithValues(t, "POST", path.Join(user, repo, "issues", issue, "times", "stopwatch", "toggle"), map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + }) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + + req = NewRequest(t, "GET", test.RedirectURL(resp)) + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + + events = htmlDoc.doc.Find(".event > span.text") + assert.Contains(t, events.Last().Text(), "stopped working") + htmlDoc.AssertElement(t, ".event .detail .octicon-clock", true) + } else { + session.MakeRequest(t, req, http.StatusNotFound) + } +} diff --git a/tests/integration/user_avatar_test.go b/tests/integration/user_avatar_test.go new file mode 100644 index 0000000000..35be840c29 --- /dev/null +++ b/tests/integration/user_avatar_test.go @@ -0,0 +1,82 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "bytes" + "image/png" + "io" + "mime/multipart" + "net/http" + "net/url" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/avatar" + + "github.com/stretchr/testify/assert" +) + +func TestUserAvatar(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo3, is an org + + seed := user2.Email + if len(seed) == 0 { + seed = user2.Name + } + + img, err := avatar.RandomImage([]byte(seed)) + if err != nil { + assert.NoError(t, err) + return + } + + session := loginUser(t, "user2") + csrf := GetCSRF(t, session, "/user/settings") + + imgData := &bytes.Buffer{} + + body := &bytes.Buffer{} + + // Setup multi-part + writer := multipart.NewWriter(body) + writer.WriteField("source", "local") + part, err := writer.CreateFormFile("avatar", "avatar-for-testuseravatar.png") + if err != nil { + assert.NoError(t, err) + return + } + + if err := png.Encode(imgData, img); err != nil { + assert.NoError(t, err) + return + } + + if _, err := io.Copy(part, imgData); err != nil { + assert.NoError(t, err) + return + } + + if err := writer.Close(); err != nil { + assert.NoError(t, err) + return + } + + req := NewRequestWithBody(t, "POST", "/user/settings/avatar", body) + req.Header.Add("X-Csrf-Token", csrf) + req.Header.Add("Content-Type", writer.FormDataContentType()) + + session.MakeRequest(t, req, http.StatusSeeOther) + + user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo3, is an org + + req = NewRequest(t, "GET", user2.AvatarLinkWithSize(0)) + _ = session.MakeRequest(t, req, http.StatusOK) + + // Can't test if the response matches because the image is re-generated on upload but checking that this at least doesn't give a 404 should be enough. + }) +} diff --git a/tests/integration/user_test.go b/tests/integration/user_test.go new file mode 100644 index 0000000000..110f5c89bf --- /dev/null +++ b/tests/integration/user_test.go @@ -0,0 +1,251 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestViewUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/user2") + MakeRequest(t, req, http.StatusOK) +} + +func TestRenameUsername(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ + "_csrf": GetCSRF(t, session, "/user/settings"), + "name": "newUsername", + "email": "user2@example.com", + "language": "en-US", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "newUsername"}) + unittest.AssertNotExistsBean(t, &user_model.User{Name: "user2"}) +} + +func TestRenameInvalidUsername(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + invalidUsernames := []string{ + "%2f*", + "%2f.", + "%2f..", + "%00", + "thisHas ASpace", + "p<A>tho>lo<gical", + } + + session := loginUser(t, "user2") + for _, invalidUsername := range invalidUsernames { + t.Logf("Testing username %s", invalidUsername) + + req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ + "_csrf": GetCSRF(t, session, "/user/settings"), + "name": invalidUsername, + "email": "user2@example.com", + }) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + assert.Contains(t, + htmlDoc.doc.Find(".ui.negative.message").Text(), + translation.NewLocale("en-US").Tr("form.alpha_dash_dot_error"), + ) + + unittest.AssertNotExistsBean(t, &user_model.User{Name: invalidUsername}) + } +} + +func TestRenameReservedUsername(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + reservedUsernames := []string{ + ".", + "..", + ".well-known", + "admin", + "api", + "assets", + "attachments", + "avatar", + "avatars", + "captcha", + "commits", + "debug", + "error", + "explore", + "favicon.ico", + "ghost", + "issues", + "login", + "manifest.json", + "metrics", + "milestones", + "new", + "notifications", + "org", + "pulls", + "raw", + "repo", + "repo-avatars", + "robots.txt", + "search", + "serviceworker.js", + "ssh_info", + "swagger.v1.json", + "user", + "v2", + } + + session := loginUser(t, "user2") + for _, reservedUsername := range reservedUsernames { + t.Logf("Testing username %s", reservedUsername) + req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ + "_csrf": GetCSRF(t, session, "/user/settings"), + "name": reservedUsername, + "email": "user2@example.com", + "language": "en-US", + }) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + + req = NewRequest(t, "GET", test.RedirectURL(resp)) + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + assert.Contains(t, + htmlDoc.doc.Find(".ui.negative.message").Text(), + translation.NewLocale("en-US").Tr("user.form.name_reserved", reservedUsername), + ) + + unittest.AssertNotExistsBean(t, &user_model.User{Name: reservedUsername}) + } +} + +func TestExportUserGPGKeys(t *testing.T) { + defer tests.PrepareTestEnv(t)() + // Export empty key list + testExportUserGPGKeys(t, "user1", `-----BEGIN PGP PUBLIC KEY BLOCK----- +Note: This user hasn't uploaded any GPG keys. + + +=twTO +-----END PGP PUBLIC KEY BLOCK----- +`) + // Import key + // User1 <user1@example.com> + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session) + testCreateGPGKey(t, session.MakeRequest, token, http.StatusCreated, `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFyy/VUBCADJ7zbM20Z1RWmFoVgp5WkQfI2rU1Vj9cQHes9i42wVLLtcbPeo +QzubgzvMPITDy7nfWxgSf83E23DoHQ1ACFbQh/6eFSRrjsusp3YQ/08NSfPPbcu8 +0M5G+VGwSfzS5uEcwBVQmHyKdcOZIERTNMtYZx1C3bjLD1XVJHvWz9D72Uq4qeO3 +8SR+lzp5n6ppUakcmRnxt3nGRBj1+hEGkdgzyPo93iy+WioegY2lwCA9xMEo5dah +BmYxWx51zyiXYlReTaxlyb3/nuSUt8IcW3Q8zjdtJj4Nu8U1SpV8EdaA1I9IPbHW +510OSLmD3XhqHH5m6mIxL1YoWxk3V7gpDROtABEBAAG0GVVzZXIxIDx1c2VyMUBl +eGFtcGxlLmNvbT6JAU4EEwEIADgWIQTQEbrYxmXsp1z3j7z9+v0I6RSEHwUCXLL9 +VQIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD9+v0I6RSEH22YCACFqL5+ +6M0m18AMC/pumcpnnmvAS1GrrKTF8nOROA1augZwp1WCNuKw2R6uOJIHANrYECSn +u7+j6GBP2gbIW8mSAzS6HWCs7GGiPpVtT4wcu8wljUI6BxjpyZtoEkriyBjt6HfK +rkegbkuySoJvjq4IcO5D1LB1JWgsUjMYQJj/ZpBIzVtjG9QtFSOiT1Hct4PoZHdC +nsdSgyCkwRZXG+u3kT/wP9F663ba4o16vYlz3dCGo66lF2tyoG3qcyZ1OUzUrnuv +96ytAzT6XIhrE0nVoBprMxFF5zExotJD3bHjcGBFNLf944bhjKee3U6t9+OsfJVC +l7N5xxIawCuTQdbfuQENBFyy/VUBCADe61yGEoTwKfsOKIhxLaNoRmD883O0tiWt +soO/HPj9dPQLTOiwXgSgSCd8C+LNxGKct87wgFozpah4tDLC6c0nALuHJ0SLbkfz +55aRhLeOOcrAydatDp72GroXzqpZ0xZBk5wjIWdgEol2GmVRM8QGbeuakU/HVz5y +lPzxUUocgdbSi3GE3zbzijQzVJdyL/kw/KP7pKT/PPKKJ2C5NQDLy0XGKEHddXGR +EWKkVlRalxq/TjfaMR0bi3MpezBsQmp99ATPO/d7trayZUxQHRtXzGFiOXfDHATr +qN730sODjqvU+mpc/SHCRwh9qWDjZRHSuKU5YDBjb5jIQJivZsQ/ABEBAAGJATYE +GAEIACAWIQTQEbrYxmXsp1z3j7z9+v0I6RSEHwUCXLL9VQIbDAAKCRD9+v0I6RSE +H7WoB/4tXl+97rQ6owPCGSVp1Xbwt2521V7COgsOFRVTRTryEWxRW8mm0S7wQvax +C0TLXKur6NVYQMn01iyL+FZzRpEWNuYF3f9QeeLJ/+l2DafESNhNTy17+RPmacK6 +21dccpqchByVw/UMDeHSyjQLiG2lxzt8Gfx2gHmSbrq3aWovTGyz6JTffZvfy/n2 +0Hm437OBPazO0gZyXhdV2PE5RSUfvAgm44235tcV5EV0d32TJDfv61+Vr2GUbah6 +7XhJ1v6JYuh8kaYaEz8OpZDeh7f6Ho6PzJrsy/TKTKhGgZNINj1iaPFyOkQgKR5M +GrE0MHOxUbc9tbtyk0F1SuzREUBH +=DDXw +-----END PGP PUBLIC KEY BLOCK----- +`) + // Export new key + testExportUserGPGKeys(t, "user1", `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBFyy/VUBCADJ7zbM20Z1RWmFoVgp5WkQfI2rU1Vj9cQHes9i42wVLLtcbPeo +QzubgzvMPITDy7nfWxgSf83E23DoHQ1ACFbQh/6eFSRrjsusp3YQ/08NSfPPbcu8 +0M5G+VGwSfzS5uEcwBVQmHyKdcOZIERTNMtYZx1C3bjLD1XVJHvWz9D72Uq4qeO3 +8SR+lzp5n6ppUakcmRnxt3nGRBj1+hEGkdgzyPo93iy+WioegY2lwCA9xMEo5dah +BmYxWx51zyiXYlReTaxlyb3/nuSUt8IcW3Q8zjdtJj4Nu8U1SpV8EdaA1I9IPbHW +510OSLmD3XhqHH5m6mIxL1YoWxk3V7gpDROtABEBAAHNGVVzZXIxIDx1c2VyMUBl +eGFtcGxlLmNvbT7CwI4EEwEIADgWIQTQEbrYxmXsp1z3j7z9+v0I6RSEHwUCXLL9 +VQIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD9+v0I6RSEH22YCACFqL5+ +6M0m18AMC/pumcpnnmvAS1GrrKTF8nOROA1augZwp1WCNuKw2R6uOJIHANrYECSn +u7+j6GBP2gbIW8mSAzS6HWCs7GGiPpVtT4wcu8wljUI6BxjpyZtoEkriyBjt6HfK +rkegbkuySoJvjq4IcO5D1LB1JWgsUjMYQJj/ZpBIzVtjG9QtFSOiT1Hct4PoZHdC +nsdSgyCkwRZXG+u3kT/wP9F663ba4o16vYlz3dCGo66lF2tyoG3qcyZ1OUzUrnuv +96ytAzT6XIhrE0nVoBprMxFF5zExotJD3bHjcGBFNLf944bhjKee3U6t9+OsfJVC +l7N5xxIawCuTQdbfzsBNBFyy/VUBCADe61yGEoTwKfsOKIhxLaNoRmD883O0tiWt +soO/HPj9dPQLTOiwXgSgSCd8C+LNxGKct87wgFozpah4tDLC6c0nALuHJ0SLbkfz +55aRhLeOOcrAydatDp72GroXzqpZ0xZBk5wjIWdgEol2GmVRM8QGbeuakU/HVz5y +lPzxUUocgdbSi3GE3zbzijQzVJdyL/kw/KP7pKT/PPKKJ2C5NQDLy0XGKEHddXGR +EWKkVlRalxq/TjfaMR0bi3MpezBsQmp99ATPO/d7trayZUxQHRtXzGFiOXfDHATr +qN730sODjqvU+mpc/SHCRwh9qWDjZRHSuKU5YDBjb5jIQJivZsQ/ABEBAAHCwHYE +GAEIACAWIQTQEbrYxmXsp1z3j7z9+v0I6RSEHwUCXLL9VQIbDAAKCRD9+v0I6RSE +H7WoB/4tXl+97rQ6owPCGSVp1Xbwt2521V7COgsOFRVTRTryEWxRW8mm0S7wQvax +C0TLXKur6NVYQMn01iyL+FZzRpEWNuYF3f9QeeLJ/+l2DafESNhNTy17+RPmacK6 +21dccpqchByVw/UMDeHSyjQLiG2lxzt8Gfx2gHmSbrq3aWovTGyz6JTffZvfy/n2 +0Hm437OBPazO0gZyXhdV2PE5RSUfvAgm44235tcV5EV0d32TJDfv61+Vr2GUbah6 +7XhJ1v6JYuh8kaYaEz8OpZDeh7f6Ho6PzJrsy/TKTKhGgZNINj1iaPFyOkQgKR5M +GrE0MHOxUbc9tbtyk0F1SuzREUBH +=WFf5 +-----END PGP PUBLIC KEY BLOCK----- +`) +} + +func testExportUserGPGKeys(t *testing.T, user, expected string) { + session := loginUser(t, user) + t.Logf("Testing username %s export gpg keys", user) + req := NewRequest(t, "GET", "/"+user+".gpg") + resp := session.MakeRequest(t, req, http.StatusOK) + // t.Log(resp.Body.String()) + assert.Equal(t, expected, resp.Body.String()) +} + +func TestListStopWatches(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, owner.Name) + req := NewRequestf(t, "GET", "/user/stopwatches") + resp := session.MakeRequest(t, req, http.StatusOK) + var apiWatches []*api.StopWatch + DecodeJSON(t, resp, &apiWatches) + stopwatch := unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: owner.ID}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: stopwatch.IssueID}) + if assert.Len(t, apiWatches, 1) { + assert.EqualValues(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix()) + assert.EqualValues(t, issue.Index, apiWatches[0].IssueIndex) + assert.EqualValues(t, issue.Title, apiWatches[0].IssueTitle) + assert.EqualValues(t, repo.Name, apiWatches[0].RepoName) + assert.EqualValues(t, repo.OwnerName, apiWatches[0].RepoOwnerName) + assert.Greater(t, apiWatches[0].Seconds, int64(0)) + } +} diff --git a/tests/integration/version_test.go b/tests/integration/version_test.go new file mode 100644 index 0000000000..83be62d3f4 --- /dev/null +++ b/tests/integration/version_test.go @@ -0,0 +1,28 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestVersion(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + setting.AppVer = "test-version-1" + req := NewRequest(t, "GET", "/api/v1/version") + resp := MakeRequest(t, req, http.StatusOK) + + var version structs.ServerVersion + DecodeJSON(t, resp, &version) + assert.Equal(t, setting.AppVer, version.Version) +} diff --git a/tests/integration/view_test.go b/tests/integration/view_test.go new file mode 100644 index 0000000000..63544dbe35 --- /dev/null +++ b/tests/integration/view_test.go @@ -0,0 +1,27 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/tests" + "github.com/stretchr/testify/assert" +) + +func TestRenderFileSVGIsInImgTag(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + + req := NewRequest(t, "GET", "/user2/repo2/src/branch/master/line.svg") + resp := session.MakeRequest(t, req, http.StatusOK) + + doc := NewHTMLParser(t, resp.Body) + src, exists := doc.doc.Find(".file-view img").Attr("src") + assert.True(t, exists, "The SVG image should be in an <img> tag so that scripts in the SVG are not run") + assert.Equal(t, "/user2/repo2/raw/branch/master/line.svg", src) +} diff --git a/tests/integration/webfinger_test.go b/tests/integration/webfinger_test.go new file mode 100644 index 0000000000..bb3447c809 --- /dev/null +++ b/tests/integration/webfinger_test.go @@ -0,0 +1,69 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestWebfinger(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + setting.Federation.Enabled = true + defer func() { + setting.Federation.Enabled = false + }() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + appURL, _ := url.Parse(setting.AppURL) + + type webfingerLink struct { + Rel string `json:"rel,omitempty"` + Type string `json:"type,omitempty"` + Href string `json:"href,omitempty"` + Titles map[string]string `json:"titles,omitempty"` + Properties map[string]interface{} `json:"properties,omitempty"` + } + + type webfingerJRD struct { + Subject string `json:"subject,omitempty"` + Aliases []string `json:"aliases,omitempty"` + Properties map[string]interface{} `json:"properties,omitempty"` + Links []*webfingerLink `json:"links,omitempty"` + } + + session := loginUser(t, "user1") + + req := NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=acct:%s@%s", user.LowerName, appURL.Host)) + resp := MakeRequest(t, req, http.StatusOK) + + var jrd webfingerJRD + DecodeJSON(t, resp, &jrd) + assert.Equal(t, "acct:user2@"+appURL.Host, jrd.Subject) + assert.ElementsMatch(t, []string{user.HTMLURL(), appURL.String() + "api/v1/activitypub/user/" + url.PathEscape(user.Name)}, jrd.Aliases) + + req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=acct:%s@%s", user.LowerName, "unknown.host")) + MakeRequest(t, req, http.StatusBadRequest) + + req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=acct:%s@%s", "user31", appURL.Host)) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=acct:%s@%s", "user31", appURL.Host)) + session.MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=mailto:%s", user.Email)) + MakeRequest(t, req, http.StatusNotFound) +} diff --git a/tests/integration/xss_test.go b/tests/integration/xss_test.go new file mode 100644 index 0000000000..53b23072ad --- /dev/null +++ b/tests/integration/xss_test.go @@ -0,0 +1,40 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestXSSUserFullName(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + const fullName = `name & <script class="evil">alert('Oh no!');</script>` + + session := loginUser(t, user.Name) + req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ + "_csrf": GetCSRF(t, session, "/user/settings"), + "name": user.Name, + "full_name": fullName, + "email": user.Email, + "language": "en-US", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + req = NewRequestf(t, "GET", "/%s", user.Name) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + assert.EqualValues(t, 0, htmlDoc.doc.Find("script.evil").Length()) + assert.EqualValues(t, fullName, + htmlDoc.doc.Find("div.content").Find(".header.text.center").Text(), + ) +} diff --git a/tests/mssql.ini.tmpl b/tests/mssql.ini.tmpl new file mode 100644 index 0000000000..9cec6169f9 --- /dev/null +++ b/tests/mssql.ini.tmpl @@ -0,0 +1,110 @@ +APP_NAME = Gitea: Git with a cup of tea +RUN_MODE = prod + +[database] +DB_TYPE = mssql +HOST = {{TEST_MSSQL_HOST}} +NAME = {{TEST_MSSQL_DBNAME}} +USER = {{TEST_MSSQL_USERNAME}} +PASSWD = {{TEST_MSSQL_PASSWORD}} +SSL_MODE = disable + +[indexer] +REPO_INDEXER_ENABLED = true +REPO_INDEXER_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/indexers/repos.bleve + +[queue.issue_indexer] +PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/indexers/issues.bleve +DATADIR = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/indexers/issues.queue + +[queue] +TYPE = immediate + +[queue.code_indexer] +TYPE = immediate + +[queue.push_update] +TYPE = immediate + +[repository] +ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/gitea-repositories + +[repository.local] +LOCAL_COPY_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/tmp/local-repo + +[repository.upload] +TEMP_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/tmp/uploads + +[repository.signing] +SIGNING_KEY = none + +[server] +SSH_DOMAIN = localhost +HTTP_PORT = 3003 +ROOT_URL = http://localhost:3003/ +DISABLE_SSH = false +SSH_LISTEN_HOST = localhost +SSH_PORT = 2201 +START_SSH_SERVER = true +LFS_START_SERVER = true +OFFLINE_MODE = false +LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w +APP_DATA_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/data +BUILTIN_SSH_SERVER_USER = git +SSH_TRUSTED_USER_CA_KEYS = ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCb4DC1dMFnJ6pXWo7GMxTchtzmJHYzfN6sZ9FAPFR4ijMLfGki+olvOMO5Fql1/yGnGfbELQa1S6y4shSvj/5K+zUFScmEXYf3Gcr87RqilLkyk16RS+cHNB1u87xTHbETaa3nyCJeGQRpd4IQ4NKob745mwDZ7jQBH8AZEng50Oh8y8fi8skBBBzaYp1ilgvzG740L7uex6fHV62myq0SXeCa+oJUjq326FU8y+Vsa32H8A3e7tOgXZPdt2TVNltx2S9H2WO8RMi7LfaSwARNfy1zu+bfR50r6ef8Yx5YKCMz4wWb1SHU1GS800mjOjlInLQORYRNMlSwR1+vLlVDciOqFapDSbj+YOVOawR0R1aqlSKpZkt33DuOBPx9qe6CVnIi7Z+Px/KqM+OLCzlLY/RS+LbxQpDWcfTVRiP+S5qRTcE3M3UioN/e0BE/1+MpX90IGpvVkA63ILYbKEa4bM3ASL7ChTCr6xN5XT+GpVJveFKK1cfNx9ExHI4rzYE= + +[attachment] +PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/data/attachments + +[mailer] +ENABLED = true +MAILER_TYPE = dummy +FROM = mssql-{{TEST_TYPE}}-test@gitea.io + +[service] +REGISTER_EMAIL_CONFIRM = false +REGISTER_MANUAL_CONFIRM = false +ENABLE_NOTIFY_MAIL = false +DISABLE_REGISTRATION = false +ENABLE_CAPTCHA = false +REQUIRE_SIGNIN_VIEW = false +DEFAULT_KEEP_EMAIL_PRIVATE = false +DEFAULT_ALLOW_CREATE_ORGANIZATION = true +NO_REPLY_ADDRESS = noreply.example.org +ENABLE_NOTIFY_MAIL = true + +[picture] +DISABLE_GRAVATAR = false +ENABLE_FEDERATED_AVATAR = false +AVATAR_UPLOAD_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/data/avatars +REPOSITORY_AVATAR_UPLOAD_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/data/repo-avatars + +[session] +PROVIDER = file +PROVIDER_CONFIG = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/data/sessions + +[log] +MODE = {{TEST_LOGGER}} +ROOT_PATH = {{REPO_TEST_DIR}}mssql-log +ROUTER = , +XORM = file +ENABLE_SSH_LOG = true + +[log.test] +LEVEL = Info +COLORIZE = true + +[log.file] +LEVEL = Debug + +[security] +DISABLE_GIT_HOOKS = false +INSTALL_LOCK = true +SECRET_KEY = 9pCviYTWSb +INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTU1NTE2MTh9.hhSVGOANkaKk3vfCd2jDOIww4pUk0xtg9JRde5UogyQ + +[lfs] +PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/data/lfs + +[packages] +ENABLED = true diff --git a/tests/mysql.ini.tmpl b/tests/mysql.ini.tmpl new file mode 100644 index 0000000000..24a9a02dc4 --- /dev/null +++ b/tests/mysql.ini.tmpl @@ -0,0 +1,126 @@ +APP_NAME = Gitea: Git with a cup of tea +RUN_MODE = prod + +[database] +DB_TYPE = mysql +HOST = {{TEST_MYSQL_HOST}} +NAME = {{TEST_MYSQL_DBNAME}} +USER = {{TEST_MYSQL_USERNAME}} +PASSWD = {{TEST_MYSQL_PASSWORD}} +SSL_MODE = disable + +[indexer] +REPO_INDEXER_ENABLED = true +REPO_INDEXER_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql/indexers/repos.bleve + +[queue.issue_indexer] +TYPE = elasticsearch +CONN_STR = http://elastic:changeme@elasticsearch:9200 +DATADIR = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql/indexers/issues.queue + +[queue] +TYPE = immediate + +[queue.code_indexer] +TYPE = immediate + +[queue.push_update] +TYPE = immediate + +[repository] +ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql/gitea-repositories + +[repository.local] +LOCAL_COPY_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql/tmp/local-repo + +[repository.upload] +TEMP_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql/tmp/uploads + +[repository.signing] +SIGNING_KEY = none + +[server] +SSH_DOMAIN = localhost +HTTP_PORT = 3001 +ROOT_URL = http://localhost:3001/ +DISABLE_SSH = false +SSH_LISTEN_HOST = localhost +SSH_PORT = 2201 +APP_DATA_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql/data +BUILTIN_SSH_SERVER_USER = git +START_SSH_SERVER = true +OFFLINE_MODE = false + +LFS_START_SERVER = true +LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w +SSH_TRUSTED_USER_CA_KEYS = ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCb4DC1dMFnJ6pXWo7GMxTchtzmJHYzfN6sZ9FAPFR4ijMLfGki+olvOMO5Fql1/yGnGfbELQa1S6y4shSvj/5K+zUFScmEXYf3Gcr87RqilLkyk16RS+cHNB1u87xTHbETaa3nyCJeGQRpd4IQ4NKob745mwDZ7jQBH8AZEng50Oh8y8fi8skBBBzaYp1ilgvzG740L7uex6fHV62myq0SXeCa+oJUjq326FU8y+Vsa32H8A3e7tOgXZPdt2TVNltx2S9H2WO8RMi7LfaSwARNfy1zu+bfR50r6ef8Yx5YKCMz4wWb1SHU1GS800mjOjlInLQORYRNMlSwR1+vLlVDciOqFapDSbj+YOVOawR0R1aqlSKpZkt33DuOBPx9qe6CVnIi7Z+Px/KqM+OLCzlLY/RS+LbxQpDWcfTVRiP+S5qRTcE3M3UioN/e0BE/1+MpX90IGpvVkA63ILYbKEa4bM3ASL7ChTCr6xN5XT+GpVJveFKK1cfNx9ExHI4rzYE= + +[lfs] +MINIO_BASE_PATH = lfs/ + +[attachment] +MINIO_BASE_PATH = attachments/ + +[avatars] +MINIO_BASE_PATH = avatars/ + +[repo-avatars] +MINIO_BASE_PATH = repo-avatars/ + +[storage] +STORAGE_TYPE = minio +SERVE_DIRECT = false +MINIO_ENDPOINT = minio:9000 +MINIO_ACCESS_KEY_ID = 123456 +MINIO_SECRET_ACCESS_KEY = 12345678 +MINIO_BUCKET = gitea +MINIO_LOCATION = us-east-1 +MINIO_USE_SSL = false + +[mailer] +ENABLED = true +MAILER_TYPE = dummy +FROM = mysql-{{TEST_TYPE}}-test@gitea.io + +[service] +REGISTER_EMAIL_CONFIRM = false +REGISTER_MANUAL_CONFIRM = false +ENABLE_NOTIFY_MAIL = false +DISABLE_REGISTRATION = false +ENABLE_CAPTCHA = false +REQUIRE_SIGNIN_VIEW = false +DEFAULT_KEEP_EMAIL_PRIVATE = false +DEFAULT_ALLOW_CREATE_ORGANIZATION = true +NO_REPLY_ADDRESS = noreply.example.org +ENABLE_NOTIFY_MAIL = true + +[picture] +DISABLE_GRAVATAR = false +ENABLE_FEDERATED_AVATAR = false + +[session] +PROVIDER = file +PROVIDER_CONFIG = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql/data/sessions + +[log] +MODE = {{TEST_LOGGER}} +ROOT_PATH = {{REPO_TEST_DIR}}mysql-log +ROUTER = , +XORM = file +ENABLE_SSH_LOG = true + +[log.test] +LEVEL = Info +COLORIZE = true + +[log.file] +LEVEL = Debug + +[security] +DISABLE_GIT_HOOKS = false +INSTALL_LOCK = true +SECRET_KEY = 9pCviYTWSb +INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTU1NTE2MTh9.hhSVGOANkaKk3vfCd2jDOIww4pUk0xtg9JRde5UogyQ + +[packages] +ENABLED = true diff --git a/tests/mysql8.ini.tmpl b/tests/mysql8.ini.tmpl new file mode 100644 index 0000000000..f290efe1dc --- /dev/null +++ b/tests/mysql8.ini.tmpl @@ -0,0 +1,107 @@ +APP_NAME = Gitea: Git with a cup of tea +RUN_MODE = prod + +[database] +DB_TYPE = mysql +HOST = {{TEST_MYSQL8_HOST}} +NAME = {{TEST_MYSQL8_DBNAME}} +USER = {{TEST_MYSQL8_USERNAME}} +PASSWD = {{TEST_MYSQL8_PASSWORD}} +SSL_MODE = disable + +[indexer] +REPO_INDEXER_ENABLED = true +REPO_INDEXER_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql8/indexers/repos.bleve + +[queue.issue_indexer] +PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql8/indexers/issues.bleve +DATADIR = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql8/indexers/issues.queue + +[queue] +TYPE = immediate + +[queue.code_indexer] +TYPE = immediate + +[queue.push_update] +TYPE = immediate + +[repository] +ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql8/gitea-repositories + +[repository.local] +LOCAL_COPY_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql8/tmp/local-repo + +[repository.upload] +TEMP_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql8/tmp/uploads + +[repository.signing] +SIGNING_KEY = none + +[server] +SSH_DOMAIN = localhost +HTTP_PORT = 3004 +ROOT_URL = http://localhost:3004/ +DISABLE_SSH = false +SSH_LISTEN_HOST = localhost +SSH_PORT = 2204 +START_SSH_SERVER = true +LFS_START_SERVER = true +OFFLINE_MODE = false +LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w +APP_DATA_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql8/data +BUILTIN_SSH_SERVER_USER = git +SSH_TRUSTED_USER_CA_KEYS = ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCb4DC1dMFnJ6pXWo7GMxTchtzmJHYzfN6sZ9FAPFR4ijMLfGki+olvOMO5Fql1/yGnGfbELQa1S6y4shSvj/5K+zUFScmEXYf3Gcr87RqilLkyk16RS+cHNB1u87xTHbETaa3nyCJeGQRpd4IQ4NKob745mwDZ7jQBH8AZEng50Oh8y8fi8skBBBzaYp1ilgvzG740L7uex6fHV62myq0SXeCa+oJUjq326FU8y+Vsa32H8A3e7tOgXZPdt2TVNltx2S9H2WO8RMi7LfaSwARNfy1zu+bfR50r6ef8Yx5YKCMz4wWb1SHU1GS800mjOjlInLQORYRNMlSwR1+vLlVDciOqFapDSbj+YOVOawR0R1aqlSKpZkt33DuOBPx9qe6CVnIi7Z+Px/KqM+OLCzlLY/RS+LbxQpDWcfTVRiP+S5qRTcE3M3UioN/e0BE/1+MpX90IGpvVkA63ILYbKEa4bM3ASL7ChTCr6xN5XT+GpVJveFKK1cfNx9ExHI4rzYE= + +[attachment] +PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql8/data/attachments + +[mailer] +ENABLED = false + +[service] +REGISTER_EMAIL_CONFIRM = false +REGISTER_MANUAL_CONFIRM = false +ENABLE_NOTIFY_MAIL = false +DISABLE_REGISTRATION = false +ENABLE_CAPTCHA = false +REQUIRE_SIGNIN_VIEW = false +DEFAULT_KEEP_EMAIL_PRIVATE = false +DEFAULT_ALLOW_CREATE_ORGANIZATION = true +NO_REPLY_ADDRESS = noreply.example.org + +[picture] +DISABLE_GRAVATAR = false +ENABLE_FEDERATED_AVATAR = false +AVATAR_UPLOAD_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql8/data/avatars +REPOSITORY_AVATAR_UPLOAD_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql8/data/repo-avatars + +[session] +PROVIDER = file +PROVIDER_CONFIG = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql8/data/sessions + +[log] +MODE = {{TEST_LOGGER}} +ROOT_PATH = {{REPO_TEST_DIR}}mysql8-log +ROUTER = , +XORM = file +ENABLE_SSH_LOG = true + +[log.test] +LEVEL = Info +COLORIZE = true + +[log.file] +LEVEL = Debug + +[security] +DISABLE_GIT_HOOKS = false +INSTALL_LOCK = true +SECRET_KEY = 9pCviYTWSb +INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTU1NTE2MTh9.hhSVGOANkaKk3vfCd2jDOIww4pUk0xtg9JRde5UogyQ + +[lfs] +PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql8/data/lfs + +[packages] +ENABLED = true diff --git a/tests/pgsql.ini.tmpl b/tests/pgsql.ini.tmpl new file mode 100644 index 0000000000..c39b6a79c3 --- /dev/null +++ b/tests/pgsql.ini.tmpl @@ -0,0 +1,111 @@ +APP_NAME = Gitea: Git with a cup of tea +RUN_MODE = prod + +[database] +DB_TYPE = postgres +HOST = {{TEST_PGSQL_HOST}} +NAME = {{TEST_PGSQL_DBNAME}} +USER = {{TEST_PGSQL_USERNAME}} +PASSWD = {{TEST_PGSQL_PASSWORD}} +SCHEMA = {{TEST_PGSQL_SCHEMA}} +SSL_MODE = disable + +[indexer] +REPO_INDEXER_ENABLED = true +REPO_INDEXER_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/indexers/repos.bleve + +[queue.issue_indexer] +PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/indexers/issues.bleve +DATADIR = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/indexers/issues.queue + +[queue] +TYPE = immediate + +[queue.code_indexer] +TYPE = immediate + +[queue.push_update] +TYPE = immediate + +[repository] +ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/gitea-repositories + +[repository.local] +LOCAL_COPY_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/tmp/local-repo + +[repository.upload] +TEMP_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/tmp/uploads + +[repository.signing] +SIGNING_KEY = none + +[server] +SSH_DOMAIN = localhost +HTTP_PORT = 3002 +ROOT_URL = http://localhost:3002/ +DISABLE_SSH = false +SSH_LISTEN_HOST = localhost +SSH_PORT = 2202 +START_SSH_SERVER = true +LFS_START_SERVER = true +OFFLINE_MODE = false +LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w +APP_DATA_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/data +BUILTIN_SSH_SERVER_USER = git +SSH_TRUSTED_USER_CA_KEYS = ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCb4DC1dMFnJ6pXWo7GMxTchtzmJHYzfN6sZ9FAPFR4ijMLfGki+olvOMO5Fql1/yGnGfbELQa1S6y4shSvj/5K+zUFScmEXYf3Gcr87RqilLkyk16RS+cHNB1u87xTHbETaa3nyCJeGQRpd4IQ4NKob745mwDZ7jQBH8AZEng50Oh8y8fi8skBBBzaYp1ilgvzG740L7uex6fHV62myq0SXeCa+oJUjq326FU8y+Vsa32H8A3e7tOgXZPdt2TVNltx2S9H2WO8RMi7LfaSwARNfy1zu+bfR50r6ef8Yx5YKCMz4wWb1SHU1GS800mjOjlInLQORYRNMlSwR1+vLlVDciOqFapDSbj+YOVOawR0R1aqlSKpZkt33DuOBPx9qe6CVnIi7Z+Px/KqM+OLCzlLY/RS+LbxQpDWcfTVRiP+S5qRTcE3M3UioN/e0BE/1+MpX90IGpvVkA63ILYbKEa4bM3ASL7ChTCr6xN5XT+GpVJveFKK1cfNx9ExHI4rzYE= + +[attachment] +PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/data/attachments + +[mailer] +ENABLED = true +MAILER_TYPE = dummy +FROM = pgsql-{{TEST_TYPE}}-test@gitea.io + +[service] +REGISTER_EMAIL_CONFIRM = false +REGISTER_MANUAL_CONFIRM = false +ENABLE_NOTIFY_MAIL = false +DISABLE_REGISTRATION = false +ENABLE_CAPTCHA = false +REQUIRE_SIGNIN_VIEW = false +DEFAULT_KEEP_EMAIL_PRIVATE = false +DEFAULT_ALLOW_CREATE_ORGANIZATION = true +NO_REPLY_ADDRESS = noreply.example.org +ENABLE_NOTIFY_MAIL = true + +[picture] +DISABLE_GRAVATAR = false +ENABLE_FEDERATED_AVATAR = false +AVATAR_UPLOAD_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/data/avatars +REPOSITORY_AVATAR_UPLOAD_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/data/repo-avatars + +[session] +PROVIDER = file +PROVIDER_CONFIG = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/data/sessions + +[log] +MODE = {{TEST_LOGGER}} +ROOT_PATH = {{REPO_TEST_DIR}}pgsql-log +ROUTER = , +XORM = file +ENABLE_SSH_LOG = true + +[log.test] +LEVEL = Info +COLORIZE = true + +[log.file] +LEVEL = Debug + +[security] +DISABLE_GIT_HOOKS = false +INSTALL_LOCK = true +SECRET_KEY = 9pCviYTWSb +INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTU1NTE2MTh9.hhSVGOANkaKk3vfCd2jDOIww4pUk0xtg9JRde5UogyQ + +[lfs] +PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/data/lfs + +[packages] +ENABLED = true diff --git a/tests/sqlite.ini.tmpl b/tests/sqlite.ini.tmpl new file mode 100644 index 0000000000..f5e8895e06 --- /dev/null +++ b/tests/sqlite.ini.tmpl @@ -0,0 +1,109 @@ +APP_NAME = Gitea: Git with a cup of tea +RUN_MODE = prod + +[database] +DB_TYPE = sqlite3 +PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/gitea.db + +[indexer] +REPO_INDEXER_ENABLED = true +REPO_INDEXER_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/indexers/repos.bleve + +[queue.issue_indexer] +PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/indexers/issues.bleve +DATADIR = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/indexers/issues.queue + +[queue] +TYPE = immediate + +[queue.code_indexer] +TYPE = immediate + +[queue.push_update] +TYPE = immediate + +[repository] +ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/gitea-repositories + +[repository.local] +LOCAL_COPY_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/tmp/local-repo + +[repository.upload] +TEMP_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/tmp/uploads + +[repository.signing] +SIGNING_KEY = none + +[server] +SSH_DOMAIN = localhost +HTTP_PORT = 3003 +ROOT_URL = http://localhost:3003/ +DISABLE_SSH = false +SSH_LISTEN_HOST = localhost +SSH_PORT = 2203 +START_SSH_SERVER = true +LFS_START_SERVER = true +OFFLINE_MODE = false +LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w +APP_DATA_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/data +ENABLE_GZIP = true +BUILTIN_SSH_SERVER_USER = git +SSH_TRUSTED_USER_CA_KEYS = ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCb4DC1dMFnJ6pXWo7GMxTchtzmJHYzfN6sZ9FAPFR4ijMLfGki+olvOMO5Fql1/yGnGfbELQa1S6y4shSvj/5K+zUFScmEXYf3Gcr87RqilLkyk16RS+cHNB1u87xTHbETaa3nyCJeGQRpd4IQ4NKob745mwDZ7jQBH8AZEng50Oh8y8fi8skBBBzaYp1ilgvzG740L7uex6fHV62myq0SXeCa+oJUjq326FU8y+Vsa32H8A3e7tOgXZPdt2TVNltx2S9H2WO8RMi7LfaSwARNfy1zu+bfR50r6ef8Yx5YKCMz4wWb1SHU1GS800mjOjlInLQORYRNMlSwR1+vLlVDciOqFapDSbj+YOVOawR0R1aqlSKpZkt33DuOBPx9qe6CVnIi7Z+Px/KqM+OLCzlLY/RS+LbxQpDWcfTVRiP+S5qRTcE3M3UioN/e0BE/1+MpX90IGpvVkA63ILYbKEa4bM3ASL7ChTCr6xN5XT+GpVJveFKK1cfNx9ExHI4rzYE= + +[attachment] +PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/data/attachments + +[mailer] +ENABLED = true +MAILER_TYPE = dummy +FROM = sqlite-{{TEST_TYPE}}-test@gitea.io + +[service] +REGISTER_EMAIL_CONFIRM = false +REGISTER_MANUAL_CONFIRM = false +ENABLE_NOTIFY_MAIL = true +DISABLE_REGISTRATION = false +ENABLE_CAPTCHA = false +REQUIRE_SIGNIN_VIEW = false +DEFAULT_KEEP_EMAIL_PRIVATE = false +DEFAULT_ALLOW_CREATE_ORGANIZATION = true +NO_REPLY_ADDRESS = noreply.example.org + +[picture] +DISABLE_GRAVATAR = false +ENABLE_FEDERATED_AVATAR = false +AVATAR_UPLOAD_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/data/avatars +REPOSITORY_AVATAR_UPLOAD_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/data/repo-avatars + +[session] +PROVIDER = file +PROVIDER_CONFIG = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/data/sessions + +[log] +MODE = {{TEST_LOGGER}} +ROOT_PATH = {{REPO_TEST_DIR}}sqlite-log +ROUTER = , +XORM = file +ENABLE_SSH_LOG = true + +[log.test] +LEVEL = Info +COLORIZE = true + +[log.file] +LEVEL = Debug + +[security] +DISABLE_GIT_HOOKS = false +INSTALL_LOCK = true +SECRET_KEY = 9pCviYTWSb +INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTI3OTU5ODN9.OQkH5UmzID2XBdwQ9TAI6Jj2t1X-wElVTjbE7aoN4I8 + +[oauth2] +JWT_SECRET = KZb_QLUd4fYVyxetjxC4eZkrBgWM2SndOOWDNtgUUko + +[lfs] +PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/data/lfs + +[packages] +ENABLED = true diff --git a/tests/test_utils.go b/tests/test_utils.go new file mode 100644 index 0000000000..0662530002 --- /dev/null +++ b/tests/test_utils.go @@ -0,0 +1,224 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package tests + +import ( + "context" + "database/sql" + "fmt" + "os" + "path" + "path/filepath" + "runtime" + "testing" + + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/queue" + repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/routers" + + "github.com/stretchr/testify/assert" +) + +func InitTest(requireGitea bool) { + giteaRoot := base.SetupGiteaRoot() + if giteaRoot == "" { + fmt.Println("Environment variable $GITEA_ROOT not set") + os.Exit(1) + } + if requireGitea { + giteaBinary := "gitea" + if runtime.GOOS == "windows" { + giteaBinary += ".exe" + } + setting.AppPath = path.Join(giteaRoot, giteaBinary) + if _, err := os.Stat(setting.AppPath); err != nil { + fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath) + os.Exit(1) + } + } + + giteaConf := os.Getenv("GITEA_CONF") + if giteaConf == "" { + fmt.Println("Environment variable $GITEA_CONF not set") + os.Exit(1) + } else if !path.IsAbs(giteaConf) { + setting.CustomConf = path.Join(giteaRoot, giteaConf) + } else { + setting.CustomConf = giteaConf + } + + setting.SetCustomPathAndConf("", "", "") + setting.LoadForTest() + setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master" + _ = util.RemoveAll(repo_module.LocalCopyPath()) + + if err := git.InitFull(context.Background()); err != nil { + log.Fatal("git.InitOnceWithSync: %v", err) + } + + setting.InitDBConfig() + if err := storage.Init(); err != nil { + fmt.Printf("Init storage failed: %v", err) + os.Exit(1) + } + + switch { + case setting.Database.UseMySQL: + connType := "tcp" + if len(setting.Database.Host) > 0 && setting.Database.Host[0] == '/' { // looks like a unix socket + connType = "unix" + } + + db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@%s(%s)/", + setting.Database.User, setting.Database.Passwd, connType, setting.Database.Host)) + defer db.Close() + if err != nil { + log.Fatal("sql.Open: %v", err) + } + if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)); err != nil { + log.Fatal("db.Exec: %v", err) + } + case setting.Database.UsePostgreSQL: + var db *sql.DB + var err error + if setting.Database.Host[0] == '/' { + db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@/%s?sslmode=%s&host=%s", + setting.Database.User, setting.Database.Passwd, setting.Database.Name, setting.Database.SSLMode, setting.Database.Host)) + } else { + db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", + setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode)) + } + + defer db.Close() + if err != nil { + log.Fatal("sql.Open: %v", err) + } + dbrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM pg_database WHERE datname = '%s'", setting.Database.Name)) + if err != nil { + log.Fatal("db.Query: %v", err) + } + defer dbrows.Close() + + if !dbrows.Next() { + if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)); err != nil { + log.Fatal("db.Exec: CREATE DATABASE: %v", err) + } + } + // Check if we need to setup a specific schema + if len(setting.Database.Schema) == 0 { + break + } + db.Close() + + if setting.Database.Host[0] == '/' { + db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@/%s?sslmode=%s&host=%s", + setting.Database.User, setting.Database.Passwd, setting.Database.Name, setting.Database.SSLMode, setting.Database.Host)) + } else { + db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", + setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode)) + } + // This is a different db object; requires a different Close() + defer db.Close() + if err != nil { + log.Fatal("sql.Open: %v", err) + } + schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema)) + if err != nil { + log.Fatal("db.Query: %v", err) + } + defer schrows.Close() + + if !schrows.Next() { + // Create and setup a DB schema + if _, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema)); err != nil { + log.Fatal("db.Exec: CREATE SCHEMA: %v", err) + } + } + + case setting.Database.UseMSSQL: + host, port := setting.ParseMSSQLHostPort(setting.Database.Host) + db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", + host, port, "master", setting.Database.User, setting.Database.Passwd)) + if err != nil { + log.Fatal("sql.Open: %v", err) + } + if _, err := db.Exec(fmt.Sprintf("If(db_id(N'%s') IS NULL) BEGIN CREATE DATABASE %s; END;", setting.Database.Name, setting.Database.Name)); err != nil { + log.Fatal("db.Exec: %v", err) + } + defer db.Close() + } + + routers.GlobalInitInstalled(graceful.GetManager().HammerContext()) +} + +func PrepareTestEnv(t testing.TB, skip ...int) func() { + t.Helper() + ourSkip := 2 + if len(skip) > 0 { + ourSkip += skip[0] + } + deferFn := PrintCurrentTest(t, ourSkip) + assert.NoError(t, unittest.LoadFixtures()) + assert.NoError(t, util.RemoveAll(setting.RepoRootPath)) + assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) + ownerDirs, err := os.ReadDir(setting.RepoRootPath) + if err != nil { + assert.NoError(t, err, "unable to read the new repo root: %v\n", err) + } + for _, ownerDir := range ownerDirs { + if !ownerDir.Type().IsDir() { + continue + } + repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name())) + if err != nil { + assert.NoError(t, err, "unable to read the new repo root: %v\n", err) + } + for _, repoDir := range repoDirs { + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0o755) + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0o755) + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0o755) + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0o755) + } + } + + return deferFn +} + +// resetFixtures flushes queues, reloads fixtures and resets test repositories within a single test. +// Most tests should call defer tests.PrepareTestEnv(t)() (or have onGiteaRun do that for them) but sometimes +// within a single test this is required +func ResetFixtures(t *testing.T) { + assert.NoError(t, queue.GetManager().FlushAll(context.Background(), -1)) + assert.NoError(t, unittest.LoadFixtures()) + assert.NoError(t, util.RemoveAll(setting.RepoRootPath)) + assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) + ownerDirs, err := os.ReadDir(setting.RepoRootPath) + if err != nil { + assert.NoError(t, err, "unable to read the new repo root: %v\n", err) + } + for _, ownerDir := range ownerDirs { + if !ownerDir.Type().IsDir() { + continue + } + repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name())) + if err != nil { + assert.NoError(t, err, "unable to read the new repo root: %v\n", err) + } + for _, repoDir := range repoDirs { + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0o755) + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0o755) + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0o755) + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0o755) + } + } +} diff --git a/tests/testlogger.go b/tests/testlogger.go new file mode 100644 index 0000000000..b65573a771 --- /dev/null +++ b/tests/testlogger.go @@ -0,0 +1,207 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package tests + +import ( + "context" + "fmt" + "os" + "runtime" + "strings" + "sync" + "testing" + "time" + + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/queue" +) + +var ( + prefix string + SlowTest = 10 * time.Second + SlowFlush = 5 * time.Second +) + +// TestLogger is a logger which will write to the testing log +type TestLogger struct { + log.WriterLogger +} + +var WriterCloser = &testLoggerWriterCloser{} + +type testLoggerWriterCloser struct { + sync.RWMutex + t []*testing.TB +} + +func (w *testLoggerWriterCloser) setT(t *testing.TB) { + w.Lock() + w.t = append(w.t, t) + w.Unlock() +} + +func (w *testLoggerWriterCloser) Write(p []byte) (int, error) { + w.RLock() + var t *testing.TB + if len(w.t) > 0 { + t = w.t[len(w.t)-1] + } + w.RUnlock() + if t != nil && *t != nil { + if len(p) > 0 && p[len(p)-1] == '\n' { + p = p[:len(p)-1] + } + + defer func() { + err := recover() + if err == nil { + return + } + var errString string + errErr, ok := err.(error) + if ok { + errString = errErr.Error() + } else { + errString, ok = err.(string) + } + if !ok { + panic(err) + } + if !strings.HasPrefix(errString, "Log in goroutine after ") { + panic(err) + } + }() + + (*t).Log(string(p)) + return len(p), nil + } + return len(p), nil +} + +func (w *testLoggerWriterCloser) Close() error { + w.Lock() + if len(w.t) > 0 { + w.t = w.t[:len(w.t)-1] + } + w.Unlock() + return nil +} + +func (w *testLoggerWriterCloser) Reset() { + w.Lock() + if len(w.t) > 0 { + for _, t := range w.t { + if t == nil { + continue + } + fmt.Fprintf(os.Stdout, "Unclosed logger writer in test: %s", (*t).Name()) + (*t).Errorf("Unclosed logger writer in test: %s", (*t).Name()) + } + w.t = nil + } + w.Unlock() +} + +// PrintCurrentTest prints the current test to os.Stdout +func PrintCurrentTest(t testing.TB, skip ...int) func() { + start := time.Now() + actualSkip := 1 + if len(skip) > 0 { + actualSkip = skip[0] + } + _, filename, line, _ := runtime.Caller(actualSkip) + + if log.CanColorStdout { + fmt.Fprintf(os.Stdout, "=== %s (%s:%d)\n", fmt.Formatter(log.NewColoredValue(t.Name())), strings.TrimPrefix(filename, prefix), line) + } else { + fmt.Fprintf(os.Stdout, "=== %s (%s:%d)\n", t.Name(), strings.TrimPrefix(filename, prefix), line) + } + WriterCloser.setT(&t) + return func() { + took := time.Since(start) + if took > SlowTest { + if log.CanColorStdout { + fmt.Fprintf(os.Stdout, "+++ %s is a slow test (took %v)\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgYellow)), fmt.Formatter(log.NewColoredValue(took, log.Bold, log.FgYellow))) + } else { + fmt.Fprintf(os.Stdout, "+++ %s is a slow test (took %v)\n", t.Name(), took) + } + } + timer := time.AfterFunc(SlowFlush, func() { + if log.CanColorStdout { + fmt.Fprintf(os.Stdout, "+++ %s ... still flushing after %v ...\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgRed)), SlowFlush) + } else { + fmt.Fprintf(os.Stdout, "+++ %s ... still flushing after %v ...\n", t.Name(), SlowFlush) + } + }) + if err := queue.GetManager().FlushAll(context.Background(), 2*time.Minute); err != nil { + t.Errorf("Flushing queues failed with error %v", err) + } + timer.Stop() + flushTook := time.Since(start) - took + if flushTook > SlowFlush { + if log.CanColorStdout { + fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgRed)), fmt.Formatter(log.NewColoredValue(flushTook, log.Bold, log.FgRed))) + } else { + fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", t.Name(), flushTook) + } + } + _ = WriterCloser.Close() + } +} + +// Printf takes a format and args and prints the string to os.Stdout +func Printf(format string, args ...interface{}) { + if log.CanColorStdout { + for i := 0; i < len(args); i++ { + args[i] = log.NewColoredValue(args[i]) + } + } + fmt.Fprintf(os.Stdout, "\t"+format, args...) +} + +// NewTestLogger creates a TestLogger as a log.LoggerProvider +func NewTestLogger() log.LoggerProvider { + logger := &TestLogger{} + logger.Colorize = log.CanColorStdout + logger.Level = log.TRACE + return logger +} + +// Init inits connection writer with json config. +// json config only need key "level". +func (log *TestLogger) Init(config string) error { + err := json.Unmarshal([]byte(config), log) + if err != nil { + return err + } + log.NewWriterLogger(WriterCloser) + return nil +} + +// Content returns the content accumulated in the content provider +func (log *TestLogger) Content() (string, error) { + return "", fmt.Errorf("not supported") +} + +// Flush when log should be flushed +func (log *TestLogger) Flush() { +} + +// ReleaseReopen does nothing +func (log *TestLogger) ReleaseReopen() error { + return nil +} + +// GetName returns the default name for this implementation +func (log *TestLogger) GetName() string { + return "test" +} + +func init() { + log.Register("test", NewTestLogger) + _, filename, _, _ := runtime.Caller(0) + prefix = strings.TrimSuffix(filename, "tests/integration/testlogger.go") +} |