diff --git a/SpeckleAutomateDotnetExample.sln b/SpeckleAutomateDotnetExample.sln index 48a9afe..e9b84bf 100644 --- a/SpeckleAutomateDotnetExample.sln +++ b/SpeckleAutomateDotnetExample.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.5.002.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpeckleAutomateDotnetExample", "SpeckleAutomateDotnetExample\SpeckleAutomateDotnetExample.csproj", "{E1DE5809-1111-4817-9B7D-7ADCA087FA1C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestAutomateFunction", "TestAutomateFunction\TestAutomateFunction.csproj", "{B74B115F-D553-4FD5-8E4C-7B13134217DA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {E1DE5809-1111-4817-9B7D-7ADCA087FA1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {E1DE5809-1111-4817-9B7D-7ADCA087FA1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {E1DE5809-1111-4817-9B7D-7ADCA087FA1C}.Release|Any CPU.Build.0 = Release|Any CPU + {B74B115F-D553-4FD5-8E4C-7B13134217DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B74B115F-D553-4FD5-8E4C-7B13134217DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B74B115F-D553-4FD5-8E4C-7B13134217DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B74B115F-D553-4FD5-8E4C-7B13134217DA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SpeckleAutomateDotnetExample/AutomateFunction.cs b/SpeckleAutomateDotnetExample/AutomateFunction.cs index 667f3f4..257da4e 100644 --- a/SpeckleAutomateDotnetExample/AutomateFunction.cs +++ b/SpeckleAutomateDotnetExample/AutomateFunction.cs @@ -4,7 +4,7 @@ using Speckle.Core.Api; using Speckle.Core.Models; using SpeckleAutomateDotnetExample; -static class AutomateFunction +public static class AutomateFunction { public static string ADDED = "ADDED"; public static string MODIFIED = "MODIFIED"; @@ -19,11 +19,9 @@ static class AutomateFunction Console.WriteLine("Starting execution"); _ = typeof(ObjectsKit).Assembly; // INFO: Force objects kit to initialize - double tolerance = functionInputs.Tolerance; - // get the testing and release branches string testBranchName = automationContext.AutomationRunData.BranchName; - string releaseBranchName = testBranchName.Replace("/testing", "/release"); + string releaseBranchName = functionInputs.DiffBranch; Console.WriteLine($"Comparing {testBranchName} against {releaseBranchName}"); Branch? releaseBranch = await automationContext.SpeckleClient .BranchGet(automationContext.AutomationRunData.ProjectId, releaseBranchName, 1) @@ -37,7 +35,7 @@ static class AutomateFunction Commit releaseCommit = releaseBranch.commits.items.First(); if (releaseCommit is null) { - throw new Exception("Release branch has no commits"); + throw new Exception("Diff branch has no commits"); } // get the test and release commit base @@ -249,6 +247,12 @@ static class AutomateFunction ); } + automationContext.AttachErrorToObjects( + "ADDED", + addedAppIdObjects.Select(o => o.Item1), + "added objects with an application Id" + ); + foreach (var deleted in deletedAppIdObjects) { Console.WriteLine( @@ -256,17 +260,22 @@ static class AutomateFunction ); } - foreach (var modified in modifiedAppIdObjects) - { - Console.WriteLine( - $"{MODIFIED} {modified.Item3} object: id( {modified.Item1} ), appId: {modified.Item2}. Changed props: {modified.Item4}" - ); - } + automationContext.AttachErrorToObjects( + "MODIFIED", + modifiedAppIdObjects.Select(o => o.Item1), + "modified objects with an application Id" + ); foreach (var changed in changedSpeckleIdObjects) { Console.WriteLine($"CHANGED {changed.Item2} object: id( {changed.Item1} )"); } + + automationContext.AttachErrorToObjects( + "CHANGED", + changedSpeckleIdObjects.Select(o => o.Item1), + "changed objects with no application Id" + ); } } } diff --git a/SpeckleAutomateDotnetExample/FunctionInputs.cs b/SpeckleAutomateDotnetExample/FunctionInputs.cs index 864a9e9..08ebd8d 100644 --- a/SpeckleAutomateDotnetExample/FunctionInputs.cs +++ b/SpeckleAutomateDotnetExample/FunctionInputs.cs @@ -5,10 +5,11 @@ using System.ComponentModel.DataAnnotations; /// /// This class is used to generate a JSON Schema to ensure that the user provided values /// are valid and match the required schema. -struct FunctionInputs +public struct FunctionInputs { - //[Required] - public double Tolerance; - - //public string Exclusions; + /// + /// The name of the branch to compare against. This should be the full branch path. + /// + [Required] + public string DiffBranch; } diff --git a/TestAutomateFunction/AutomationContextTest.cs b/TestAutomateFunction/AutomationContextTest.cs new file mode 100644 index 0000000..b587ae3 --- /dev/null +++ b/TestAutomateFunction/AutomationContextTest.cs @@ -0,0 +1,150 @@ +# nullable enable +namespace TestAutomateFunction; + +using Speckle.Automate.Sdk.Schema; +using Speckle.Automate.Sdk; +using Speckle.Core.Api; +using Speckle.Core.Credentials; +using Speckle.Core.Models; +using Speckle.Core.Transports; +using Utils = TestAutomateUtils; + +[TestFixture] +public sealed class AutomationContextTest : IDisposable +{ + // Regression testing function project id + public string projectId = "6ada180f0c"; + + // e2e release branch and model id + public string releaseBranchName = "e2e/release"; + public string releaseModelId = "e4c8cc3802"; + + // e2e testing branch and model id + public string testingBranchName = "e2e/testing"; + public string testingModelId = "c374f0aade"; + + private async Task AutomationRunData( + Base testObject, + Base releaseObject + ) + { + // send the release commit to the release branch + string releaseObjId = await Operations.Send( + releaseObject, + new List { new ServerTransport(client.Account, projectId) } + ); + + string releaseVersionId = await client.CommitCreate( + new() + { + streamId = projectId, + objectId = releaseObjId, + branchName = releaseBranchName + } + ); + + // send the test object to the test branch + string testObjId = await Operations.Send( + testObject, + new List { new ServerTransport(client.Account, projectId) } + ); + + string testingVersionId = await client.CommitCreate( + new() + { + streamId = projectId, + objectId = testObjId, + branchName = testingBranchName + } + ); + + // create a new automation on the testing branch + var automationName = TestAutomateUtils.RandomString(10); + var automationId = TestAutomateUtils.RandomString(10); + var automationRevisionId = TestAutomateUtils.RandomString(10); + + await TestAutomateUtils.RegisterNewAutomation( + projectId, + testingModelId, + client, + automationId, + automationName, + automationRevisionId + ); + + var automationRunId = TestAutomateUtils.RandomString(10); + var functionId = TestAutomateUtils.RandomString(10); + var functionName = "Automation name " + TestAutomateUtils.RandomString(10); + var functionRelease = TestAutomateUtils.RandomString(10); + + return new AutomationRunData + { + ProjectId = projectId, + ModelId = testingModelId, + BranchName = testingBranchName, + VersionId = testingVersionId, + SpeckleServerUrl = client.ServerUrl, + AutomationId = automationId, + AutomationRevisionId = automationRevisionId, + AutomationRunId = automationRunId, + FunctionId = functionId, + FunctionName = functionName, + FunctionRelease = functionRelease, + }; + } + + private Client client; + private Account account; + + private string GetSpeckleToken() + { + var envVarName = "SPECKLE_TOKEN"; + Environment.SetEnvironmentVariable(envVarName, ""); + var token = Environment.GetEnvironmentVariable(envVarName); + if (token is null) + { + throw new Exception( + $"Cannot run tests without a {envVarName} environment variable" + ); + } + + return token; + } + + private string GetSpeckleServerUrl() => + Environment.GetEnvironmentVariable("SPECKLE_SERVER_URL") + ?? "https://latest.speckle.systems"; + + [OneTimeSetUp] + public void Setup() + { + account = new Account + { + token = GetSpeckleToken(), + serverInfo = new ServerInfo { url = GetSpeckleServerUrl() } + }; + client = new Client(account); + } + + [Test] + public async Task TestFunctionRun() + { + var automationRunData = await AutomationRunData( + TestAutomateUtils.TestObject(), + TestAutomateUtils.ReleaseObject() + ); + var automationContext = await AutomationRunner.RunFunction( + AutomateFunction.Run, + automationRunData, + account.token, + new FunctionInputs { DiffBranch = releaseBranchName } + ); + + Assert.That(automationContext.RunStatus, Is.EqualTo("FAILED")); + } + + public void Dispose() + { + client.Dispose(); + } +} diff --git a/TestAutomateFunction/Properties/launchSettings.json b/TestAutomateFunction/Properties/launchSettings.json new file mode 100644 index 0000000..bd9d2d3 --- /dev/null +++ b/TestAutomateFunction/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "TestAutomateFunction": { + "commandName": "Project", + "environmentVariables": { + "SPECKLE_TOKEN": "718d0650eea8038642850414e43bd81314d740359a", + "SPECKLE_SERVER_URL": "https://latest.speckle.dev/" + } + } + } +} \ No newline at end of file diff --git a/TestAutomateFunction/TestAutomateFunction.csproj b/TestAutomateFunction/TestAutomateFunction.csproj new file mode 100644 index 0000000..ec49856 --- /dev/null +++ b/TestAutomateFunction/TestAutomateFunction.csproj @@ -0,0 +1,27 @@ + + + + net7.0 + enable + enable + + false + + 0b5cf4dd-cfa3-4982-9722-ca6c6672b8dd + + + + + + + + + + + + + + + + + diff --git a/TestAutomateFunction/TestAutomateUtils.cs b/TestAutomateFunction/TestAutomateUtils.cs new file mode 100644 index 0000000..7609a1f --- /dev/null +++ b/TestAutomateFunction/TestAutomateUtils.cs @@ -0,0 +1,98 @@ +using System.Diagnostics.CodeAnalysis; +using GraphQL; +using Speckle.Core.Api; +using Speckle.Core.Models; + +namespace TestAutomateFunction; + +public static class TestAutomateUtils +{ + [SuppressMessage("Security", "CA5394:Do not use insecure randomness")] + public static string RandomString(int length) + { + Random rand = new(); + const string pool = "abcdefghijklmnopqrstuvwxyz0123456789"; + var chars = Enumerable + .Range(0, length) + .Select(_ => pool[rand.Next(0, pool.Length)]); + return new string(chars.ToArray()); + } + + /// + /// The test object to compare against the release object + /// + /// + /// Contains 3 children objects to capture the Added, Deleted, Modified, and Unchanged categories + public static Base TestObject() + { + Base unchanged = new() { ["unchangedProp"] = true }; + Base added = new() { ["addedProp"] = "added" }; + Base modified = new() { ["modifiedProp"] = 1 }; + Base testObject = new(); + testObject["unchanged"] = unchanged; + testObject["added"] = added; + testObject["modified"] = modified; + return testObject; + } + + /// + /// The release object to compare the test object against + /// + /// Contains 3 children objects to capture the Added, Deleted, Modified, and Unchanged categories + public static Base ReleaseObject() + { + Base unchanged = new() { ["unchangedProp"] = true }; + Base deleted = new() { ["deletedProp"] = "deleted" }; + Base modified = new() { ["modifiedProp"] = 0.5 }; + Base releaseObject = new(); + releaseObject["unchanged"] = unchanged; + releaseObject["deleted"] = deleted; + releaseObject["modified"] = modified; + return releaseObject; + } + + public static async Task RegisterNewAutomation( + string projectId, + string modelId, + Client speckleClient, + string automationId, + string automationName, + string automationRevisionId + ) + { + GraphQLRequest query = + new( + query: """ + mutation CreateAutomation( + $projectId: String! + $modelId: String! + $automationName: String! + $automationId: String! + $automationRevisionId: String! + ) { + automationMutations { + create( + input: { + projectId: $projectId + modelId: $modelId + automationName: $automationName + automationId: $automationId + automationRevisionId: $automationRevisionId + } + ) + } + } + """, + variables: new + { + projectId, + modelId, + automationName, + automationId, + automationRevisionId, + } + ); + + await speckleClient.ExecuteGraphQLRequest(query); + } +} diff --git a/TestAutomateFunction/Usings.cs b/TestAutomateFunction/Usings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/TestAutomateFunction/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file