diff --git a/backend/src/Designer/Services/Implementation/SourceControlSI.cs b/backend/src/Designer/Services/Implementation/SourceControlSI.cs
index 32ab1639f7b..7f28fa9f71a 100644
--- a/backend/src/Designer/Services/Implementation/SourceControlSI.cs
+++ b/backend/src/Designer/Services/Implementation/SourceControlSI.cs
@@ -14,6 +14,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
+using Commit = LibGit2Sharp.Commit;
namespace Altinn.Studio.Designer.Services.Implementation
{
@@ -56,8 +57,12 @@ public string CloneRemoteRepository(string org, string repository)
{
string remoteRepo = FindRemoteRepoLocation(org, repository);
CloneOptions cloneOptions = new();
- cloneOptions.CredentialsProvider = (url, user, cred) => new UsernamePasswordCredentials { Username = GetAppToken(), Password = string.Empty };
- return LibGit2Sharp.Repository.Clone(remoteRepo, FindLocalRepoLocation(org, repository), cloneOptions);
+ cloneOptions.CredentialsProvider = CredentialsProvider();
+ string localPath = FindLocalRepoLocation(org, repository);
+ string cloneResult = LibGit2Sharp.Repository.Clone(remoteRepo, localPath, cloneOptions);
+
+ FetchGitNotes(localPath);
+ return cloneResult;
}
///
@@ -65,14 +70,16 @@ public string CloneRemoteRepository(string org, string repository, string destin
{
string remoteRepo = FindRemoteRepoLocation(org, repository);
CloneOptions cloneOptions = new();
- cloneOptions.CredentialsProvider = (url, user, cred) => new UsernamePasswordCredentials { Username = GetAppToken(), Password = string.Empty };
+ cloneOptions.CredentialsProvider = CredentialsProvider();
if (!string.IsNullOrEmpty(branchName))
{
cloneOptions.BranchName = branchName;
}
- return LibGit2Sharp.Repository.Clone(remoteRepo, destinationPath, cloneOptions);
+ string cloneResult = LibGit2Sharp.Repository.Clone(remoteRepo, destinationPath, cloneOptions);
+ FetchGitNotes(destinationPath);
+ return cloneResult;
}
///
@@ -89,8 +96,7 @@ public RepoStatus PullRemoteChanges(string org, string repository)
},
};
pullOptions.FetchOptions = new FetchOptions();
- pullOptions.FetchOptions.CredentialsProvider = (url, user, cred) =>
- new UsernamePasswordCredentials { Username = GetAppToken(), Password = string.Empty };
+ pullOptions.FetchOptions.CredentialsProvider = CredentialsProvider();
try
{
@@ -130,8 +136,7 @@ public void FetchRemoteChanges(string org, string repository)
using (var repo = new LibGit2Sharp.Repository(FindLocalRepoLocation(org, repository)))
{
FetchOptions fetchOptions = new();
- fetchOptions.CredentialsProvider = (url, user, cred) =>
- new UsernamePasswordCredentials { Username = GetAppToken(), Password = string.Empty };
+ fetchOptions.CredentialsProvider = CredentialsProvider();
foreach (Remote remote in repo?.Network?.Remotes)
{
@@ -166,31 +171,29 @@ public async Task Push(string org, string repository)
{
bool pushSuccess = true;
string localServiceRepoFolder = _settings.GetServicePath(org, repository, AuthenticationHelper.GetDeveloperUserName(_httpContextAccessor.HttpContext));
- using (LibGit2Sharp.Repository repo = new(localServiceRepoFolder))
+ using LibGit2Sharp.Repository repo = new(localServiceRepoFolder);
+ string remoteUrl = FindRemoteRepoLocation(org, repository);
+ Remote remote = repo.Network.Remotes["origin"];
+
+ if (!remote.PushUrl.Equals(remoteUrl))
{
- string remoteUrl = FindRemoteRepoLocation(org, repository);
- Remote remote = repo.Network.Remotes["origin"];
+ // This is relevant when we switch beteen running designer in local or in docker. The remote URL changes.
+ // Requires adminstrator access to update files.
+ repo.Network.Remotes.Update("origin", r => r.Url = remoteUrl);
+ }
- if (!remote.PushUrl.Equals(remoteUrl))
+ PushOptions options = new()
+ {
+ OnPushStatusError = pushError =>
{
- // This is relevant when we switch beteen running designer in local or in docker. The remote URL changes.
- // Requires adminstrator access to update files.
- repo.Network.Remotes.Update("origin", r => r.Url = remoteUrl);
+ _logger.LogError("Push error: {0}", pushError.Message);
+ pushSuccess = false;
}
+ };
+ options.CredentialsProvider = CredentialsProvider();
- PushOptions options = new()
- {
- OnPushStatusError = pushError =>
- {
- _logger.LogError("Push error: {0}", pushError.Message);
- pushSuccess = false;
- }
- };
- options.CredentialsProvider = (url, user, cred) =>
- new UsernamePasswordCredentials { Username = GetAppToken(), Password = string.Empty };
-
- repo.Network.Push(remote, @"refs/heads/master", options);
- }
+ repo.Network.Push(remote, @"refs/heads/master", options);
+ repo.Network.Push(remote, "refs/notes/commits", options);
return await Task.FromResult(pushSuccess);
}
@@ -201,24 +204,31 @@ public async Task Push(string org, string repository)
/// Information about the commit
public void Commit(CommitInfo commitInfo)
{
- string localServiceRepoFolder = _settings.GetServicePath(commitInfo.Org, commitInfo.Repository, AuthenticationHelper.GetDeveloperUserName(_httpContextAccessor.HttpContext));
- using (LibGit2Sharp.Repository repo = new(localServiceRepoFolder))
+ CommitAndAddStudioNote(commitInfo.Org, commitInfo.Repository, commitInfo.Message);
+ }
+
+ private void CommitAndAddStudioNote(string org, string repository, string message)
+ {
+ string localServiceRepoFolder = _settings.GetServicePath(org, repository, AuthenticationHelper.GetDeveloperUserName(_httpContextAccessor.HttpContext));
+ using LibGit2Sharp.Repository repo = new LibGit2Sharp.Repository(localServiceRepoFolder);
+ string remoteUrl = FindRemoteRepoLocation(org, repository);
+ Remote remote = repo.Network.Remotes["origin"];
+
+ if (!remote.PushUrl.Equals(remoteUrl))
{
- string remoteUrl = FindRemoteRepoLocation(commitInfo.Org, commitInfo.Repository);
- Remote remote = repo.Network.Remotes["origin"];
+ // This is relevant when we switch beteen running designer in local or in docker. The remote URL changes.
+ // Requires adminstrator access to update files.
+ repo.Network.Remotes.Update("origin", r => r.Url = remoteUrl);
+ }
- if (!remote.PushUrl.Equals(remoteUrl))
- {
- // This is relevant when we switch beteen running designer in local or in docker. The remote URL changes.
- // Requires adminstrator access to update files.
- repo.Network.Remotes.Update("origin", r => r.Url = remoteUrl);
- }
+ Commands.Stage(repo, "*");
- Commands.Stage(repo, "*");
+ LibGit2Sharp.Signature signature = GetDeveloperSignature();
+ var commit = repo.Commit(message, signature, signature);
+
+ var notes = repo.Notes;
+ notes.Add(commit.Id, "studio-commit", signature, signature, notes.DefaultNamespace);
- LibGit2Sharp.Signature signature = GetDeveloperSignature();
- repo.Commit(commitInfo.Message, signature, signature);
- }
}
///
@@ -463,39 +473,41 @@ public void VerifyCloneExists(string org, string repository)
private void CommitAndPushToBranch(string org, string repository, string branchName, string localPath, string message)
{
- using (LibGit2Sharp.Repository repo = new(localPath))
+ using LibGit2Sharp.Repository repo = new(localPath);
+ // Restrict users from empty commit
+ if (repo.RetrieveStatus().IsDirty)
{
- // Restrict users from empty commit
- if (repo.RetrieveStatus().IsDirty)
- {
- string remoteUrl = FindRemoteRepoLocation(org, repository);
- Remote remote = repo.Network.Remotes["origin"];
+ string remoteUrl = FindRemoteRepoLocation(org, repository);
+ Remote remote = repo.Network.Remotes["origin"];
- if (!remote.PushUrl.Equals(remoteUrl))
- {
- // This is relevant when we switch beteen running designer in local or in docker. The remote URL changes.
- // Requires adminstrator access to update files.
- repo.Network.Remotes.Update("origin", r => r.Url = remoteUrl);
- }
+ if (!remote.PushUrl.Equals(remoteUrl))
+ {
+ // This is relevant when we switch beteen running designer in local or in docker. The remote URL changes.
+ // Requires adminstrator access to update files.
+ repo.Network.Remotes.Update("origin", r => r.Url = remoteUrl);
+ }
- Commands.Stage(repo, "*");
+ Commands.Stage(repo, "*");
- LibGit2Sharp.Signature signature = GetDeveloperSignature();
- repo.Commit(message, signature, signature);
+ LibGit2Sharp.Signature signature = GetDeveloperSignature();
+ var commit = repo.Commit(message, signature, signature);
+ var notes = repo.Notes;
+ notes.Add(commit.Id, "studio-commit", signature, signature, notes.DefaultNamespace);
- PushOptions options = new();
- options.CredentialsProvider = (url, user, cred) =>
- new UsernamePasswordCredentials { Username = GetAppToken(), Password = string.Empty };
+ PushOptions options = new();
+ options.CredentialsProvider = CredentialsProvider();
- if (branchName == "master")
- {
- repo.Network.Push(remote, @"refs/heads/master", options);
- return;
- }
+ if (branchName == "master")
+ {
+ repo.Network.Push(remote, @"refs/heads/master", options);
+ repo.Network.Push(remote, "refs/notes/commits", options);
- Branch b = repo.Branches[branchName];
- repo.Network.Push(b, options);
+ return;
}
+
+ Branch b = repo.Branches[branchName];
+ repo.Network.Push(b, options);
+ repo.Network.Push(remote, "refs/notes/commits", options);
}
}
@@ -624,5 +636,17 @@ private LibGit2Sharp.Signature GetDeveloperSignature()
{
return new LibGit2Sharp.Signature(AuthenticationHelper.GetDeveloperUserName(_httpContextAccessor.HttpContext), "@jugglingnutcase", DateTime.Now);
}
+
+ private LibGit2Sharp.Handlers.CredentialsHandler CredentialsProvider() => (url, user, cred) => new UsernamePasswordCredentials { Username = GetAppToken(), Password = string.Empty };
+
+ private void FetchGitNotes(string localRepositoryPath)
+ {
+ using var repo = new LibGit2Sharp.Repository(localRepositoryPath);
+ var options = new FetchOptions()
+ {
+ CredentialsProvider = CredentialsProvider()
+ };
+ Commands.Fetch(repo, "origin", new List { "refs/notes/*:refs/notes/*" }, options, "fetch notes");
+ }
}
}
diff --git a/backend/tests/Designer.Tests/GiteaIntegrationTests/GiteaIntegrationTestsBase.cs b/backend/tests/Designer.Tests/GiteaIntegrationTests/GiteaIntegrationTestsBase.cs
index ae3f5cab693..072e263c12f 100644
--- a/backend/tests/Designer.Tests/GiteaIntegrationTests/GiteaIntegrationTestsBase.cs
+++ b/backend/tests/Designer.Tests/GiteaIntegrationTests/GiteaIntegrationTestsBase.cs
@@ -8,7 +8,6 @@
using Designer.Tests.Fixtures;
using DotNet.Testcontainers.Builders;
using FluentAssertions;
-using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.Mvc.Testing.Handlers;
using Microsoft.AspNetCore.TestHost;
@@ -28,6 +27,10 @@ public abstract class GiteaIntegrationTestsBase : ApiTestsBase<
private CookieContainer CookieContainer { get; } = new CookieContainer();
+ /// On some systems path too long error occurs if repo is nested deep in file system.
+ protected override string TestRepositoriesLocation =>
+ Path.Combine(Path.GetTempPath(), "altinn", "tests", "repos");
+
///
/// Used when performing chained calls to designer api
///
@@ -50,14 +53,17 @@ protected override void Dispose(bool disposing)
DeleteDirectoryIfExists(CreatedFolderPath);
}
- private static void DeleteDirectoryIfExists(string directoryPath)
+ protected static void DeleteDirectoryIfExists(string directoryPath)
{
if (string.IsNullOrWhiteSpace(directoryPath) || !Directory.Exists(directoryPath))
{
return;
}
- var directory = new DirectoryInfo(directoryPath) { Attributes = FileAttributes.Normal };
+ var directory = new DirectoryInfo(directoryPath)
+ {
+ Attributes = FileAttributes.Normal
+ };
foreach (var info in directory.GetFileSystemInfos("*", SearchOption.AllDirectories))
{
@@ -131,5 +137,31 @@ protected async Task CreateAppUsingDesigner(string org, string repoName)
response.StatusCode.Should().Be(HttpStatusCode.Created);
InvalidateAllCookies();
}
+
+ protected static string GetCommitInfoJson(string text, string org, string repository) =>
+ @$"{{
+ ""message"": ""{text}"",
+ ""org"": ""{org}"",
+ ""repository"": ""{repository}""
+ }}";
+
+ protected static string GenerateCommitJsonPayload(string text, string message) =>
+ @$"{{
+ ""author"": {{
+ ""email"": ""{GiteaConstants.AdminEmail}"",
+ ""name"": ""{GiteaConstants.AdminUser}""
+ }},
+ ""committer"": {{
+ ""email"": ""{GiteaConstants.AdminEmail}"",
+ ""name"": ""{GiteaConstants.AdminUser}""
+ }},
+ ""content"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes(text))}"",
+ ""dates"": {{
+ ""author"": ""{DateTime.Now:O}"",
+ ""committer"": ""{DateTime.Now:O}""
+ }},
+ ""message"": ""{message}"",
+ ""signoff"": true
+ }}";
}
}
diff --git a/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/CopyAppGiteaIntegrationTests.cs b/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/CopyAppGiteaIntegrationTests.cs
new file mode 100644
index 00000000000..85859106ea4
--- /dev/null
+++ b/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/CopyAppGiteaIntegrationTests.cs
@@ -0,0 +1,56 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Designer.Tests.Fixtures;
+using Designer.Tests.Utils;
+using FluentAssertions;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Polly;
+using Polly.Retry;
+using Xunit;
+
+namespace Designer.Tests.GiteaIntegrationTests.RepositoryController
+{
+ public class CopyAppGiteaIntegrationTests : GiteaIntegrationTestsBase, IClassFixture>
+ {
+
+ private string CopyRepoName { get; set; }
+
+ public CopyAppGiteaIntegrationTests(WebApplicationFactory factory, GiteaFixture giteaFixture) : base(factory, giteaFixture)
+ {
+ }
+
+ [Theory]
+ [Trait("Category", "GiteaIntegrationTest")]
+ [InlineData(GiteaConstants.TestOrgUsername)]
+ public async Task Copy_Repo_Should_Return_Created(string org)
+ {
+ string targetRepo = TestDataHelper.GenerateTestRepoName("-gitea");
+ await CreateAppUsingDesigner(org, targetRepo);
+
+ CopyRepoName = TestDataHelper.GenerateTestRepoName("-gitea-copy");
+
+ // Copy app
+ using HttpResponseMessage commitResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/copy-app?sourceRepository={targetRepo}&targetRepository={CopyRepoName}", null);
+ commitResponse.StatusCode.Should().Be(HttpStatusCode.Created);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ {
+ return;
+ }
+ if (string.IsNullOrEmpty(CopyRepoName))
+ {
+ return;
+ }
+
+ string copyRepoPath = Path.Combine(TestRepositoriesLocation, "testUser", "ttd", CopyRepoName);
+ DeleteDirectoryIfExists(copyRepoPath);
+ }
+ }
+}
diff --git a/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/GitNotesGiteaIntegrationTests.cs b/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/GitNotesGiteaIntegrationTests.cs
new file mode 100644
index 00000000000..53f68f77c98
--- /dev/null
+++ b/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/GitNotesGiteaIntegrationTests.cs
@@ -0,0 +1,174 @@
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Net.Mime;
+using System.Text;
+using System.Text.Json.Nodes;
+using System.Threading.Tasks;
+using Altinn.Studio.Designer.Models;
+using Designer.Tests.Fixtures;
+using Designer.Tests.Utils;
+using FluentAssertions;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Xunit;
+
+namespace Designer.Tests.GiteaIntegrationTests.RepositoryController
+{
+ public class GitNotesGiteaIntegrationTests : GiteaIntegrationTestsBase, IClassFixture>
+ {
+
+ public GitNotesGiteaIntegrationTests(WebApplicationFactory factory, GiteaFixture giteaFixture) : base(factory, giteaFixture)
+ {
+ }
+ [Theory]
+ [Trait("Category", "GiteaIntegrationTest")]
+ [InlineData(GiteaConstants.TestOrgUsername)]
+ public async Task Commit_AndPush_Separate_Should_Create_GitNote(string org)
+ {
+ string targetRepo = TestDataHelper.GenerateTestRepoName("-gitea");
+ await CreateAppUsingDesigner(org, targetRepo);
+
+ // Commit and push separately
+ InvalidateAllCookies();
+ await File.WriteAllTextAsync($"{CreatedFolderPath}/test3.txt", "I am a new file");
+ using var commitContent = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json);
+ using HttpResponseMessage commitResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit", commitContent);
+ commitResponse.StatusCode.Should().Be(HttpStatusCode.OK);
+
+ InvalidateAllCookies();
+ using HttpResponseMessage pushResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/push", null);
+ pushResponse.StatusCode.Should().Be(HttpStatusCode.OK);
+
+ await VerifyStudioNoteAddedToLatestCommit(org, targetRepo);
+ }
+
+ [Theory]
+ [Trait("Category", "GiteaIntegrationTest")]
+ [InlineData(GiteaConstants.TestOrgUsername)]
+ public async Task Commit_AndPush_AndContents_Should_Create_GitNote(string org)
+ {
+ string targetRepo = TestDataHelper.GenerateTestRepoName("-gitea");
+ await CreateAppUsingDesigner(org, targetRepo);
+
+ // Add a file to local repo and try to push with designer
+ await File.WriteAllTextAsync($"{CreatedFolderPath}/test.txt", "I am a new file");
+
+ InvalidateAllCookies();
+ using var commitAndPushContent = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json);
+ using HttpResponseMessage commitAndPushResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit-and-push", commitAndPushContent);
+ commitAndPushResponse.StatusCode.Should().Be(HttpStatusCode.OK);
+
+ await VerifyStudioNoteAddedToLatestCommit(org, targetRepo);
+ }
+
+ [Theory]
+ [Trait("Category", "GiteaIntegrationTest")]
+ [InlineData(GiteaConstants.TestOrgUsername)]
+ public async Task Commit_AndPush_AndContents_WorksAfterResetOfRepo(string org)
+ {
+ string targetRepo = TestDataHelper.GenerateTestRepoName("-gitea");
+ await CreateAppUsingDesigner(org, targetRepo);
+
+ // Add a file to local repo and try to push with designer
+ await File.WriteAllTextAsync($"{CreatedFolderPath}/test.txt", "I am a new file");
+
+ InvalidateAllCookies();
+ using var commitAndPushContent = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json);
+ using HttpResponseMessage commitAndPushResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit-and-push", commitAndPushContent);
+ commitAndPushResponse.StatusCode.Should().Be(HttpStatusCode.OK);
+
+ await VerifyStudioNoteAddedToLatestCommit(org, targetRepo);
+
+ // reset repo
+ InvalidateAllCookies();
+ using HttpResponseMessage resetResponse = await HttpClient.GetAsync($"designer/api/repos/repo/{org}/{targetRepo}/reset");
+ resetResponse.StatusCode.Should().Be(HttpStatusCode.OK);
+
+ // this ensures local clone
+ InvalidateAllCookies();
+ using HttpResponseMessage appDevelopmentIndes = await HttpClient.GetAsync($"editor/{org}/{targetRepo}");
+ appDevelopmentIndes.StatusCode.Should().Be(HttpStatusCode.OK);
+
+ // Try to create a new commit
+ await File.WriteAllTextAsync($"{CreatedFolderPath}/newFile.txt", "I am a new file");
+
+ InvalidateAllCookies();
+ using var commitAndPushContent2 = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json);
+ using HttpResponseMessage commitAndPushResponse2 = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit-and-push", commitAndPushContent);
+ commitAndPushResponse2.StatusCode.Should().Be(HttpStatusCode.OK);
+
+ await VerifyStudioNoteAddedToLatestCommit(org, targetRepo);
+ }
+
+ [Theory]
+ [Trait("Category", "GiteaIntegrationTest")]
+ [InlineData(GiteaConstants.TestOrgUsername)]
+ public async Task LocalAndStudioDevelopment_PullLocalCommitFirst_BehaveAsExpected(string org)
+ {
+ string targetRepo = TestDataHelper.GenerateTestRepoName("-gitea");
+ await CreateAppUsingDesigner(org, targetRepo);
+ await VerifyStudioNoteAddedToLatestCommit(org, targetRepo);
+
+ // Create a file using gitea client
+ using var createFileContent = new StringContent(GenerateCommitJsonPayload("I am a new file created in gitea", "test gitea commit"), Encoding.UTF8, MediaTypeNames.Application.Json);
+ using HttpResponseMessage createFileResponse = await GiteaFixture.GiteaClient.Value.PostAsync($"repos/{org}/{targetRepo}/contents/test2.txt", createFileContent);
+ createFileResponse.StatusCode.Should().Be(HttpStatusCode.Created);
+
+ // Try pull file with designer endpoint
+ InvalidateAllCookies();
+ using HttpResponseMessage pullResponse = await HttpClient.GetAsync($"designer/api/repos/repo/{org}/{targetRepo}/pull");
+ pullResponse.StatusCode.Should().Be(HttpStatusCode.OK);
+
+ // Add a new file and try to push with designer
+ await File.WriteAllTextAsync($"{CreatedFolderPath}/test3.txt", "I am a new file created directly with gitea");
+ InvalidateAllCookies();
+ using var commitAndPushContent = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json);
+ using HttpResponseMessage commitAndPushResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit-and-push", commitAndPushContent);
+ commitAndPushResponse.StatusCode.Should().Be(HttpStatusCode.OK);
+ await VerifyStudioNoteAddedToLatestCommit(org, targetRepo);
+ }
+
+ [Theory]
+ [Trait("Category", "GiteaIntegrationTest")]
+ [InlineData(GiteaConstants.TestOrgUsername)]
+ public async Task LocalAndStudioDevelopment_BeginEditAndPullLocalCommit(string org)
+ {
+ string targetRepo = TestDataHelper.GenerateTestRepoName("-gitea");
+ await CreateAppUsingDesigner(org, targetRepo);
+ await VerifyStudioNoteAddedToLatestCommit(org, targetRepo);
+
+ // Create a file using gitea client
+ using var createFileContent = new StringContent(GenerateCommitJsonPayload("I am a new file created in gitea", "test gitea commit"), Encoding.UTF8, MediaTypeNames.Application.Json);
+ using HttpResponseMessage createFileResponse = await GiteaFixture.GiteaClient.Value.PostAsync($"repos/{org}/{targetRepo}/contents/test2.txt", createFileContent);
+ createFileResponse.StatusCode.Should().Be(HttpStatusCode.Created);
+
+ // Add a new file and try to push with designer
+ await File.WriteAllTextAsync($"{CreatedFolderPath}/test3.txt", "I am a new file created directly with gitea");
+
+ // Try pull file with designer endpoint
+ InvalidateAllCookies();
+ using HttpResponseMessage pullResponse = await HttpClient.GetAsync($"designer/api/repos/repo/{org}/{targetRepo}/pull");
+ pullResponse.StatusCode.Should().Be(HttpStatusCode.OK);
+
+
+ InvalidateAllCookies();
+ using var commitAndPushContent = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json);
+ using HttpResponseMessage commitAndPushResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit-and-push", commitAndPushContent);
+ commitAndPushResponse.StatusCode.Should().Be(HttpStatusCode.OK);
+ await VerifyStudioNoteAddedToLatestCommit(org, targetRepo);
+ }
+
+ private async Task VerifyStudioNoteAddedToLatestCommit(string org, string targetRepo)
+ {
+ InvalidateAllCookies();
+ // Check if note is added to a commit
+ using HttpResponseMessage getCommitResponse = await HttpClient.GetAsync($"designer/api/repos/repo/{org}/{targetRepo}/latest-commit");
+ Commit commit = await getCommitResponse.Content.ReadAsAsync();
+
+ var noteResponse = await GiteaFixture.GiteaClient.Value.GetAsync($"repos/{org}/{targetRepo}/git/notes/{commit.Sha}");
+
+ var notesNode = JsonNode.Parse(await noteResponse.Content.ReadAsStringAsync());
+ notesNode!["message"]!.ToString().Should().Be("studio-commit");
+ }
+ }
+}
diff --git a/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryControllerGiteaIntegrationTests.cs b/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/RepositoryControllerGiteaIntegrationTests.cs
similarity index 88%
rename from backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryControllerGiteaIntegrationTests.cs
rename to backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/RepositoryControllerGiteaIntegrationTests.cs
index b5635df8ca0..42dbbddfa31 100644
--- a/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryControllerGiteaIntegrationTests.cs
+++ b/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/RepositoryControllerGiteaIntegrationTests.cs
@@ -7,7 +7,6 @@
using System.Net.Mime;
using System.Text;
using System.Threading.Tasks;
-using Altinn.Studio.Designer.Controllers;
using Altinn.Studio.Designer.Enums;
using Altinn.Studio.Designer.Models;
using Altinn.Studio.Designer.RepositoryClient.Model;
@@ -19,7 +18,7 @@
using Polly.Retry;
using Xunit;
-namespace Designer.Tests.GiteaIntegrationTests
+namespace Designer.Tests.GiteaIntegrationTests.RepositoryController
{
public class RepositoryControllerGiteaIntegrationTests : GiteaIntegrationTestsBase, IClassFixture>
{
@@ -104,7 +103,7 @@ public async Task Pull_ShouldBeAsExpected(string org)
await CreateAppUsingDesigner(org, targetRepo);
// Create a file in gitea
- using var createFileContent = new StringContent(CreateFileJsonPayload("I am a new file created in gitea", "test commit"), Encoding.UTF8, MediaTypeNames.Application.Json);
+ using var createFileContent = new StringContent(GenerateCommitJsonPayload("I am a new file created in gitea", "test commit"), Encoding.UTF8, MediaTypeNames.Application.Json);
using HttpResponseMessage createFileResponse = await GiteaFixture.GiteaClient.Value.PostAsync($"repos/{org}/{targetRepo}/contents/test2.txt", createFileContent);
createFileResponse.StatusCode.Should().Be(HttpStatusCode.Created);
@@ -222,7 +221,7 @@ public async Task Commit_AndPush_NonPulled_ShouldReturnConflict(string org)
await CreateAppUsingDesigner(org, targetRepo);
// Create a file in gitea
- using var createFileContent = new StringContent(CreateFileJsonPayload("I am a new file created in gitea", "test commit"), Encoding.UTF8, MediaTypeNames.Application.Json);
+ using var createFileContent = new StringContent(GenerateCommitJsonPayload("I am a new file created in gitea", "test commit"), Encoding.UTF8, MediaTypeNames.Application.Json);
using HttpResponseMessage createFileResponse = await GiteaFixture.GiteaClient.Value.PostAsync($"repos/{org}/{targetRepo}/contents/test2.txt", createFileContent);
createFileResponse.StatusCode.Should().Be(HttpStatusCode.Created);
@@ -232,33 +231,6 @@ public async Task Commit_AndPush_NonPulled_ShouldReturnConflict(string org)
using var commitAndPushContent = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json);
using HttpResponseMessage commitAndPushResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit-and-push", commitAndPushContent);
commitAndPushResponse.StatusCode.Should().Be(HttpStatusCode.Conflict);
-
}
-
- private static string GetCommitInfoJson(string text, string org, string repository) =>
- @$"{{
- ""message"": ""{text}"",
- ""org"": ""{org}"",
- ""repository"": ""{repository}""
- }}";
-
- private static string CreateFileJsonPayload(string text, string message) =>
- @$"{{
- ""author"": {{
- ""email"": ""{GiteaConstants.AdminEmail}"",
- ""name"": ""{GiteaConstants.AdminUser}""
- }},
- ""committer"": {{
- ""email"": ""{GiteaConstants.AdminEmail}"",
- ""name"": ""{GiteaConstants.AdminUser}""
- }},
- ""content"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes(text))}"",
- ""dates"": {{
- ""author"": ""{DateTime.Now:O}"",
- ""committer"": ""{DateTime.Now:O}""
- }},
- ""message"": ""{message}"",
- ""signoff"": true
- }}";
}
}