From 302f508a5a5af22e43ef00898dcba7349b07285c Mon Sep 17 00:00:00 2001 From: operate-services-sdk-bot Date: Mon, 22 Apr 2024 16:12:53 +0000 Subject: [PATCH] Release v1.4.0 --- CHANGELOG.md | 8 + Directory.Build.props | 5 + Third Party Notices.md | 16 + .../Deploy/IProjectAccessDeploymentHandler.cs | 16 - .../Deploy/ProjectAccessDeploymentHandler.cs | 326 --------- .../DuplicateAuthoringStatementsException.cs | 47 -- .../ErrorHandling/InvalidDataException.cs | 33 - .../ProjectAccessPolicyDeploymentException.cs | 27 - .../Fetch/ProjectAccessFetchHandler.cs | 260 ------- .../Json/IJsonConverter.cs | 8 - .../Model/AccessControlStatement.cs | 104 --- .../Model/IAcessControlStatement.cs | 17 - .../Model/IProjectAccessFile.cs | 12 - .../Model/IProjectAccessMerger.cs | 13 - .../Model/IProjectAccessParser.cs | 9 - .../Model/ProjectAccessFile.cs | 32 - .../Model/ProjectAccessFileContent.cs | 24 - .../Model/ProjectAccessFileExtension.cs | 63 -- .../Model/ProjectAccessMerger.cs | 41 -- .../Model/ProjectAccessParser.cs | 23 - .../Results/DeployResult.cs | 14 - .../Results/FetchResult.cs | 21 - .../Results/Result.cs | 27 - .../Service/IProjectAccessClient.cs | 16 - .../IProjectAccessConfigValidator.cs | 17 - .../ProjectAccessConfigValidator.cs | 133 ---- .../Deploy/AccessConfigLoaderTests.cs | 4 +- .../Deploy/AuthoringResultTests.cs | 2 +- .../Deploy/JsonConverterTests.cs | 2 +- .../Deploy/ProjectAccessClientTests.cs | 2 +- .../ProjectAccessDeploymentHandlerTests.cs | 10 +- .../ProjectAccessDeploymentServiceTests.cs | 11 +- .../Deploy/ProjectAccessFetchHandlerTests.cs | 12 +- .../Deploy/ProjectAccessFetchServiceTests.cs | 8 +- .../Model/ProjectAccessFileExtensionTests.cs | 2 +- .../Unity.Services.Cli.Access.UnitTest.csproj | 2 +- .../Utils/TestMocks.cs | 4 +- .../Unity.Services.Cli.Access/AccessModule.cs | 14 +- .../Deploy/AccessAuthoringResult.cs | 2 +- .../Deploy/AccessConfigLoader.cs | 8 +- .../Deploy/IAccessConfigLoader.cs | 2 +- .../Deploy/JsonConverter.cs | 2 +- .../Deploy/LoadResult.cs | 4 +- .../Deploy/ProjectAccessClient.cs | 6 +- .../Deploy/ProjectAccessDeploymentService.cs | 8 +- .../Deploy/ProjectAccessFetchService.cs | 8 +- .../IO/FileSystem.cs | 2 +- .../Models/NewProjectAccessFile.cs | 4 +- .../Unity.Services.Cli.Access.csproj | 4 +- ...ity.Services.Cli.Authoring.UnitTest.csproj | 2 +- .../Exceptions/DeployException.cs | 3 - .../Exceptions/InvalidExtensionException.cs | 3 - .../Exceptions/PathNotFoundException.cs | 3 - .../Unity.Services.Cli.Authoring.csproj | 6 +- ...ity.Services.Cli.CloudCode.UnitTest.csproj | 2 +- .../Exceptions/ScriptEvaluationException.cs | 5 +- .../IO/FileSystem.cs | 5 + .../Parameters/CloudCodeScriptParser.cs | 2 +- .../Parameters/CloudScriptParametersParser.cs | 10 +- .../Unity.Services.Cli.CloudCode.csproj | 4 +- .../CloudContentDeliveryModuleTests.cs | 79 +++ .../CloudContentDeliveryTestsConstants.cs | 56 ++ .../Badges/CreateBadgeHandlerTests.cs | 122 ++++ .../Badges/DeleteBadgeHandlerTests.cs | 109 +++ .../Handlers/Badges/ListBadgeHandlerTests.cs | 129 ++++ .../Buckets/CreateBucketHandlerTests.cs | 100 +++ .../Buckets/DeleteBucketHandlerTests.cs | 98 +++ .../Handlers/Buckets/GetBucketHandlerTests.cs | 93 +++ .../Buckets/ListBucketHandlerTests.cs | 115 ++++ .../Buckets/PermissionBucketHandlerTests.cs | 113 +++ .../Buckets/PromoteBucketHandlerTests.cs | 142 ++++ .../Buckets/PromotionBucketHandlerTests.cs | 105 +++ .../Handlers/Entries/CopyEntryHandlerTests.cs | 116 ++++ .../Entries/DeleteEntryHandlerTests.cs | 105 +++ .../Entries/DownloadEntryHandlerTests.cs | 108 +++ .../Handlers/Entries/GetEntryHandlerTests.cs | 109 +++ .../Handlers/Entries/ListEntryHandlerTests.cs | 138 ++++ .../Handlers/Entries/SyncEntryHandlerTests.cs | 199 ++++++ .../Entries/UpdateEntryHandlerTests.cs | 115 ++++ .../Models/ModelsTests.cs | 53 ++ .../Service/BadgeClientTests.cs | 214 ++++++ .../Service/BucketClientTests.cs | 344 ++++++++++ .../Service/ContentDeliveryValidatorTests.cs | 78 +++ .../Service/EntryClientTests.cs | 519 ++++++++++++++ .../Service/ReleaseClientTests.cs | 221 ++++++ .../Service/SynchronizationServiceTests.cs | 530 +++++++++++++++ .../Service/UploadContentClientTests.cs | 91 +++ ...s.Cli.CloudContentDelivery.UnitTest.csproj | 26 + .../Utils/CcdUtilsTests.cs | 106 +++ .../CloudContentDeliveryModule.cs | 638 +++++++++++++++++ .../CloudContentDeliveryException.cs | 25 + .../Fetch/CloudContentDeliveryFetchService.cs | 78 +++ .../Handlers/Badges/CreateBadgeHandler.cs | 67 ++ .../Handlers/Badges/DeleteBadgeHandler.cs | 61 ++ .../Handlers/Badges/ListBadgeHandler.cs | 80 +++ .../Handlers/Buckets/CreateBucketHandler.cs | 54 ++ .../Handlers/Buckets/DeleteBucketHandler.cs | 47 ++ .../Handlers/Buckets/GetBucketHandler.cs | 49 ++ .../Handlers/Buckets/ListBucketHandler.cs | 66 ++ .../Buckets/PermissionBucketHandler.cs | 53 ++ .../Handlers/Buckets/PromoteBucketHandler.cs | 79 +++ .../Buckets/PromotionBucketHandler.cs | 57 ++ .../Handlers/Entries/CopyEntryHandler.cs | 70 ++ .../Handlers/Entries/DeleteEntryHandler.cs | 61 ++ .../Handlers/Entries/DownloadEntryHandler.cs | 78 +++ .../Handlers/Entries/GetEntryHandler.cs | 65 ++ .../Handlers/Entries/ListEntryHandler.cs | 87 +++ .../Handlers/Entries/SyncEntryHandler.cs | 168 +++++ .../Handlers/Entries/UpdateEntryHandler.cs | 69 ++ .../IO/FileSystem.cs | 7 + .../Input/CloudContentDeliveryInput.cs | 487 +++++++++++++ .../Input/CloudContentDeliveryInputBuckets.cs | 87 +++ .../Model/BadgeResult.cs | 38 ++ .../Model/BucketResult.cs | 65 ++ .../Model/EntryResult.cs | 67 ++ .../Model/ListBadgeResult.cs | 27 + .../Model/ListBucketResult.cs | 25 + .../Model/ListEntryResult.cs | 25 + .../Model/ListReleaseResult.cs | 27 + .../Model/LongOperationSummary.cs | 62 ++ .../Model/PermissionResult.cs | 36 + .../Model/PromoteResult.cs | 30 + .../Model/PromotionResult.cs | 44 ++ .../Model/ReleaseResult.cs | 68 ++ .../Model/SharedRateLimitStatus.cs | 13 + .../Model/ShortOperationSummary.cs | 44 ++ .../Model/SyncEntry.cs | 42 ++ .../Model/SyncResult.cs | 31 + .../Service/BadgeClient.cs | 103 +++ .../Service/BucketClient.cs | 296 ++++++++ .../Service/ClientWrapper.cs | 22 + .../Service/ContentDeliveryValidator.cs | 40 ++ .../Service/EntryClient.cs | 270 ++++++++ .../Service/IBadgeClient.cs | 36 + .../Service/IBucketClient.cs | 70 ++ .../Service/IClientWrapper.cs | 9 + .../Service/IContentDeliveryValidator.cs | 9 + .../Service/IEntryClient.cs | 67 ++ .../Service/IOperationSummary.cs | 15 + .../Service/IReleaseClient.cs | 60 ++ .../Service/ISynchronizationService.cs | 46 ++ .../Service/IUploadContentClient.cs | 16 + .../Service/ReleaseClient.cs | 195 ++++++ .../Service/SynchronizationService.cs | 642 ++++++++++++++++++ .../Service/UploadContentClient.cs | 59 ++ ...y.Services.Cli.CloudContentDelivery.csproj | 32 + .../Utils/CcdUtils.cs | 50 ++ .../CommonModuleTests.cs | 10 +- .../Console/CliPromptTest.cs | 2 +- .../Console/ConsoleTableTests.cs | 35 + .../Handlers/DeleteHandlerTests.cs | 54 +- .../Unity.Services.Cli.Common.UnitTest.csproj | 2 +- .../Validator/ConfigurationValidatorTests.cs | 1 + .../Unity.Services.Cli.Common/CommonModule.cs | 27 +- .../Configuration/Handlers/DeleteHandler.cs | 20 +- .../Configuration/Models/Configuration.cs | 13 +- .../Configuration/Models/Keys.cs | 51 +- .../Validator/ConfigurationValidator.cs | 30 +- .../Console/CliPrompt.cs | 18 - .../Console/ConsolePrompt.cs | 65 ++ .../Console/ConsoleTable.cs | 59 ++ .../{ICliPrompt.cs => IConsolePrompt.cs} | 24 +- .../Console/IConsoleTable.cs | 15 + .../Console/ILoadingIndicator.cs | 2 +- .../Console/IProgressBar.cs | 2 +- .../Console/LoadingIndicator.cs | 6 +- .../Console/ProgressBar.cs | 10 +- .../Exceptions/CliException.cs | 3 - .../Exceptions/DeploymentFailureException.cs | 3 - .../Process/CliProcess.cs | 4 +- .../Process/ProcessException.cs | 5 - .../Unity.Services.Cli.Common.csproj | 4 +- ...Unity.Services.Cli.Economy.UnitTest.csproj | 2 +- .../Exceptions/InvalidResourceException.cs | 1 - .../Unity.Services.Cli.Economy.csproj | 4 +- .../Handlers/ListHandlerTests.cs | 130 +++- ...y.Services.Cli.Environment.UnitTest.csproj | 2 +- .../EnvironmentModule.cs | 11 +- .../Handlers/ListHandler.cs | 68 +- .../Unity.Services.Cli.Environment.csproj | 4 +- .../Services/BuildClientTests.cs | 3 +- .../Services/CcdCloudStorageTests.cs | 20 +- .../Services/DummyBinaryBuilderTests.cs | 2 +- .../Services/FleetsClientTests.cs | 60 +- ...ices.Cli.GameServerHosting.UnitTest.csproj | 2 +- .../Exceptions/DuplicateResourceException.cs | 3 - .../Exceptions/InvalidConfigException.cs | 3 - .../Exceptions/InvalidExtensionException.cs | 3 - .../Exceptions/InvalidIdsListException.cs | 2 - .../InvalidKeyValuePairException.cs | 2 - .../Exceptions/InvalidResponseException.cs | 2 - .../Exceptions/MissingInputException.cs | 2 - .../Exceptions/PathNotFoundException.cs | 3 - .../Exceptions/SyncFailedException.cs | 3 - .../GameServerHostingModule.cs | 1 + .../Services/CcdCloudStorageClient.cs | 18 +- .../Services/DummyBinaryBuilder.cs | 5 +- .../Services/FleetClient.cs | 128 +++- ...nity.Services.Cli.GameServerHosting.csproj | 6 +- .../Common/CommonKeys.cs | 2 +- .../ServiceMocks/AccessApiMock.cs | 28 +- .../ServiceMocks/CloudCode/CloudCodeV1Mock.cs | 8 +- .../CloudContentDeliveryApiMock.cs | 35 +- .../ServiceMocks/CloudSaveApiMock.cs | 62 +- .../ServiceMocks/LeaderboardApiMock.cs | 55 +- .../RemoteConfig/RemoteConfigMock.cs | 18 +- ...Services.Cli.Integration.MockServer.csproj | 9 +- ...vices.Cli.Integration.MockServerApp.csproj | 2 +- .../AccessTests/AccessTests.cs | 2 - .../ProjectAccess/ProjectAccessDeployTests.cs | 1 - .../Authoring/Fetch/LeaderboardFetchTests.cs | 22 +- .../Fetch/ProjectAccessFetchTests.cs | 2 +- .../Authoring/Fetch/TriggersFetchTests.cs | 18 +- .../CloudContentDeliveryBadgeTests.cs | 108 +++ .../CloudContentDeliveryBucketTests.cs | 155 +++++ .../CloudContentDeliveryEntryTests.cs | 183 +++++ .../CloudContentDeliveryReleaseTests.cs | 127 ++++ .../ConfigTests/ConfigTests.cs | 2 +- .../EnvTests/EnvTests.cs | 9 +- .../Unity.Services.Cli.IntegrationTest.csproj | 4 +- ....Services.Cli.Leaderboards.UnitTest.csproj | 9 +- .../Deploy/LeaderboardsSerializer.cs | 82 +++ .../Unity.Services.Cli.Leaderboards.csproj | 4 +- .../Unity.Services.Cli.Lobby.UnitTest.csproj | 2 +- .../Unity.Services.Cli.Lobby.csproj | 2 +- .../Unity.Services.Cli.Player.UnitTest.csproj | 2 +- .../Unity.Services.Cli.Player.csproj | 2 +- ....Services.Cli.RemoteConfig.UnitTest.csproj | 2 +- .../Exceptions/ApiException.cs | 3 - .../Unity.Services.Cli.RemoteConfig.csproj | 2 +- .../Deploy/SchedulerDeploymentHandlerTests.cs | 12 +- .../Deploy/SchedulerFetchHandlerTests.cs | 15 +- ...ity.Services.Cli.Scheduler.UnitTest.csproj | 3 +- .../Deploy/SchedulerClient.cs | 4 +- .../Deploy/SchedulerDeployFetchBase.cs | 6 +- .../Exceptions/SchedulerException.cs | 3 - .../Unity.Services.Cli.Scheduler.csproj | 2 +- .../AuthenticationModuleTests.cs | 2 +- .../Authenticator/AuthenticatorV1Tests.cs | 10 +- ...rviceAccountAuthentication.UnitTest.csproj | 2 +- .../AuthenticationModule.cs | 2 +- .../Authenticator/AuthenticatorV1.cs | 8 +- ...es.Cli.ServiceAccountAuthentication.csproj | 2 +- .../MockHelper.cs | 12 +- .../Unity.Services.Cli.TestUtils.csproj | 2 +- ...nity.Services.Cli.Triggers.UnitTest.csproj | 2 +- .../Exceptions/TriggersException.cs | 3 - .../Unity.Services.Cli.Triggers.csproj | 2 +- Unity.Services.Cli/Unity.Services.Cli.sln | 77 ++- .../Unity.Services.Cli/Program.cs | 7 + .../Unity.Services.Cli.csproj | 7 +- .../CloudContentDeliveryDeploymentHandler.cs | 148 ++++ .../Deploy/DeployResult.cs | 14 + .../ICloudContentDeliveryDeploymentHandler.cs | 15 + .../Fetch/CloudContentDeliveryFetchHandler.cs | 162 +++++ .../Fetch/FetchResult.cs | 14 + .../ICloudContentDeliveryFetchHandler.cs} | 9 +- .../IO/IFileSystem.cs | 2 +- .../Model/IBucket.cs | 10 + .../Model/Statuses.cs | 20 + .../Service/ICloudContentDeliveryClient.cs | 19 + ...loudContentDelivery.Authoring.Core.csproj} | 6 +- .../DuplicateResourceValidation.cs | 42 ++ ...vices.ModuleTemplate.Authoring.Core.csproj | 4 +- .../Fetch/SchedulerFetchHandler.cs | 2 +- .../Model/Statuses.cs | 24 +- ...y.Services.Scheduler.Authoring.Core.csproj | 4 +- ...ty.Services.Triggers.Authoring.Core.csproj | 4 +- test.runsettings | 2 +- 269 files changed, 11781 insertions(+), 1847 deletions(-) create mode 100644 Directory.Build.props delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Deploy/IProjectAccessDeploymentHandler.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Deploy/ProjectAccessDeploymentHandler.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/ErrorHandling/DuplicateAuthoringStatementsException.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/ErrorHandling/InvalidDataException.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/ErrorHandling/ProjectAccessPolicyDeploymentException.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Fetch/ProjectAccessFetchHandler.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Json/IJsonConverter.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/AccessControlStatement.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IAcessControlStatement.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IProjectAccessFile.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IProjectAccessMerger.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IProjectAccessParser.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessFile.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessFileContent.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessFileExtension.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessMerger.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessParser.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Results/DeployResult.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Results/FetchResult.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Results/Result.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Service/IProjectAccessClient.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Validations/IProjectAccessConfigValidator.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Validations/ProjectAccessConfigValidator.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/CloudContentDeliveryModuleTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/CloudContentDeliveryTestsConstants.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Badges/CreateBadgeHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Badges/DeleteBadgeHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Badges/ListBadgeHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/CreateBucketHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/DeleteBucketHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/GetBucketHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/ListBucketHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/PermissionBucketHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/PromoteBucketHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/PromotionBucketHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/CopyEntryHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/DeleteEntryHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/DownloadEntryHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/GetEntryHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/ListEntryHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/SyncEntryHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/UpdateEntryHandlerTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Models/ModelsTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/BadgeClientTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/BucketClientTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/ContentDeliveryValidatorTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/EntryClientTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/ReleaseClientTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/SynchronizationServiceTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/UploadContentClientTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Unity.Services.Cli.CloudContentDelivery.UnitTest.csproj create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Utils/CcdUtilsTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/CloudContentDeliveryModule.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Exceptions/CloudContentDeliveryException.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Fetch/CloudContentDeliveryFetchService.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Badges/CreateBadgeHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Badges/DeleteBadgeHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Badges/ListBadgeHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/CreateBucketHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/DeleteBucketHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/GetBucketHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/ListBucketHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/PermissionBucketHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/PromoteBucketHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/PromotionBucketHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/CopyEntryHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/DeleteEntryHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/DownloadEntryHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/GetEntryHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/ListEntryHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/SyncEntryHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/UpdateEntryHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/IO/FileSystem.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Input/CloudContentDeliveryInput.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Input/CloudContentDeliveryInputBuckets.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/BadgeResult.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/BucketResult.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/EntryResult.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListBadgeResult.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListBucketResult.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListEntryResult.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListReleaseResult.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/LongOperationSummary.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/PermissionResult.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/PromoteResult.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/PromotionResult.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ReleaseResult.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/SharedRateLimitStatus.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ShortOperationSummary.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/SyncEntry.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/SyncResult.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/BadgeClient.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/BucketClient.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ClientWrapper.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ContentDeliveryValidator.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/EntryClient.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IBadgeClient.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IBucketClient.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IClientWrapper.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IContentDeliveryValidator.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IEntryClient.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IOperationSummary.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IReleaseClient.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ISynchronizationService.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IUploadContentClient.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ReleaseClient.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/SynchronizationService.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/UploadContentClient.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Unity.Services.Cli.CloudContentDelivery.csproj create mode 100644 Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Utils/CcdUtils.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Console/ConsoleTableTests.cs delete mode 100644 Unity.Services.Cli/Unity.Services.Cli.Common/Console/CliPrompt.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.Common/Console/ConsolePrompt.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.Common/Console/ConsoleTable.cs rename Unity.Services.Cli/Unity.Services.Cli.Common/Console/{ICliPrompt.cs => IConsolePrompt.cs} (56%) create mode 100644 Unity.Services.Cli/Unity.Services.Cli.Common/Console/IConsoleTable.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryBadgeTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryBucketTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryEntryTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryReleaseTests.cs create mode 100644 Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Deploy/CloudContentDeliveryDeploymentHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Deploy/DeployResult.cs create mode 100644 Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Deploy/ICloudContentDeliveryDeploymentHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Fetch/CloudContentDeliveryFetchHandler.cs create mode 100644 Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Fetch/FetchResult.cs rename Unity.Services.Cli/{Unity.Services.Access.Authoring.Core/Fetch/IProjectAccessFetchHandler.cs => Unity.Services.CloudContentDelivery.Authoring.Core/Fetch/ICloudContentDeliveryFetchHandler.cs} (54%) rename Unity.Services.Cli/{Unity.Services.Access.Authoring.Core => Unity.Services.CloudContentDelivery.Authoring.Core}/IO/IFileSystem.cs (87%) create mode 100644 Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Model/IBucket.cs create mode 100644 Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Model/Statuses.cs create mode 100644 Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Service/ICloudContentDeliveryClient.cs rename Unity.Services.Cli/{Unity.Services.Access.Authoring.Core/Unity.Services.Access.Authoring.Core.csproj => Unity.Services.CloudContentDelivery.Authoring.Core/Unity.Services.CloudContentDelivery.Authoring.Core.csproj} (85%) create mode 100644 Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Validations/DuplicateResourceValidation.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a2835a..6ea7259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to UGS CLI will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [1.4.0] - 2024-04-22 +### Fixed +- Improve Cloud Code script in-script parameter wrong argument type parsing error +- Cloud Content Delivery Module Service commands. Run `ugs ccd -h` to show usage. + +### Changed +- The env list command now outputs as a table + ## [1.3.0] - 2024-02-29 ### Added - Added new service module Scheduler diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..e5f48a1 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,5 @@ + + + false + + \ No newline at end of file diff --git a/Third Party Notices.md b/Third Party Notices.md index 5a82d2f..7a53c2e 100644 --- a/Third Party Notices.md +++ b/Third Party Notices.md @@ -580,3 +580,19 @@ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +
+ +Component Name: MimeMapping + +Licence Type: MIT + +Copyright (c) 2016 Matthew Little + +https://github.com/zone117x/MimeMapping + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Deploy/IProjectAccessDeploymentHandler.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Deploy/IProjectAccessDeploymentHandler.cs deleted file mode 100644 index 17e47e4..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Deploy/IProjectAccessDeploymentHandler.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Unity.Services.Access.Authoring.Core.Model; -using Unity.Services.Access.Authoring.Core.Results; - -namespace Unity.Services.Access.Authoring.Core.Deploy -{ - public interface IProjectAccessDeploymentHandler - { - Task DeployAsync( - IReadOnlyList files, - bool dryRun = false, - bool reconcile = false); - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Deploy/ProjectAccessDeploymentHandler.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Deploy/ProjectAccessDeploymentHandler.cs deleted file mode 100644 index d0aa3b3..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Deploy/ProjectAccessDeploymentHandler.cs +++ /dev/null @@ -1,326 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Unity.Services.Access.Authoring.Core.ErrorHandling; -using Unity.Services.DeploymentApi.Editor; -using Unity.Services.Access.Authoring.Core.Model; -using Unity.Services.Access.Authoring.Core.Results; -using Unity.Services.Access.Authoring.Core.Service; -using Unity.Services.Access.Authoring.Core.Validations; - -namespace Unity.Services.Access.Authoring.Core.Deploy -{ - public class ProjectAccessDeploymentHandler : IProjectAccessDeploymentHandler - { - readonly IProjectAccessClient m_ProjectAccessClient; - readonly IProjectAccessConfigValidator m_ProjectAccessConfigValidator; - readonly IProjectAccessMerger m_ProjectAccessMerger; - - public ProjectAccessDeploymentHandler( - IProjectAccessClient projectAccessClient, - IProjectAccessConfigValidator projectAccessConfigValidator, - IProjectAccessMerger projectAccessMerger) - { - m_ProjectAccessClient = projectAccessClient; - m_ProjectAccessConfigValidator = projectAccessConfigValidator; - m_ProjectAccessMerger = projectAccessMerger; - } - - public async Task DeployAsync( - IReadOnlyList files, - bool dryRun = false, - bool reconcile = false) - { - var res = new DeployResult(); - - if (!dryRun) - { - SetStartDeployingStatus(files); - } - - var deploymentExceptions = new List(); - - var validProjectAccessFiles = files - .Where((t, i) => m_ProjectAccessConfigValidator.Validate(t, deploymentExceptions)) - .ToList(); - - var validLocalStatements = m_ProjectAccessConfigValidator.FilterNonDuplicatedAuthoringStatements( - validProjectAccessFiles, - deploymentExceptions); - - var serverProjectAccessPolicyResult = await GetServerProjectAccessPolicies(files, dryRun); - var remoteStatements = serverProjectAccessPolicyResult ?? new List(); - - var localSet = validLocalStatements.Select(l => l.Sid).ToHashSet(); - var remoteSet = remoteStatements.Select(l => l.Sid).ToHashSet(); - - var toUpdate = FindStatementsToUpdate(remoteSet, validLocalStatements); - var toDelete = FindStatementsToDelete(remoteStatements, localSet, reconcile); - var toCreate = FindStatementsToCreate(remoteSet, validLocalStatements); - - var toDeploy = m_ProjectAccessMerger.MergeStatementsToDeploy( - toCreate, - toUpdate, - toDelete, - remoteStatements); - - var failedFiles = FindFailedFiles(deploymentExceptions); - var filesToDeploy = FindFilesToDeploy(files, toDeploy); - var deployedFiles = filesToDeploy.Except(failedFiles).ToList(); - - res.Created = toCreate; - res.Deleted = toDelete; - res.Updated = toUpdate; - res.Deployed = deployedFiles; - res.Failed = failedFiles; - - if (dryRun) - { - SetStatuses(res); - return res; - } - - try - { - if (reconcile && toDelete.Count != 0) - { - await m_ProjectAccessClient.DeleteAsync(toDelete.ToList()); - } - - await m_ProjectAccessClient.UpsertAsync(toDeploy.ToList()); - - SetSuccessfulDeployStatuses(filesToDeploy, res, remoteStatements); - } - catch (ProjectAccessPolicyDeploymentException e) - { - deploymentExceptions.Add(e); - e.AffectedFiles.AddRange(filesToDeploy); - res.Failed = FindFailedFiles(deploymentExceptions); - res.Deployed = FindFilesToDeploy(files, toDeploy).Except(res.Failed).ToList(); - } - catch (Exception e) - { - SetFailedStatus(filesToDeploy, DeploymentStatus.FailedToDeploy.Message, e.Message); - res.Failed = filesToDeploy; - res.Deployed = Array.Empty(); - } - - HandleDeploymentException(deploymentExceptions); - return res; - } - - static void SetStatuses(DeployResult res) - { - SetStatus( - res.Created, - "Created", - string.Empty, - SeverityLevel.Info); - SetStatus( - res.Updated, - "Updated", - string.Empty, - SeverityLevel.Info); - SetStatus( - res.Deleted, - "Deleted", - string.Empty, - SeverityLevel.Info); - } - - static void SetSuccessfulDeployStatuses(List filesToDeploy, DeployResult res, List remoteStatements) - { - foreach (var f in filesToDeploy) - f.Status = new DeploymentStatus("Deployed", "Deployed Successfully"); - - SetStatus( - res.Created, - "Created", - string.Empty, - SeverityLevel.Info); - SetStatus( - res.Updated.Where(s => s.HasStatementChanged(remoteStatements)).ToList(), - "Updated", - string.Empty, - SeverityLevel.Info); - SetStatus( - res.Updated.Where(s => !s.HasStatementChanged(remoteStatements)).ToList(), - "Updated", - "Statement was unchanged", - SeverityLevel.Info); - SetStatus( - res.Deleted, - "Deleted", - string.Empty, - SeverityLevel.Info); - } - - static List FindStatementsToUpdate( - HashSet remoteSet, - IReadOnlyList local) - { - var toUpdate = local - .Where(k => remoteSet.Contains(k.Sid)) - .ToList(); - - return toUpdate; - } - - static List FindStatementsToCreate( - HashSet remoteSet, - IReadOnlyList local) - { - return local - .Where(k => !remoteSet.Contains(k.Sid)) - .ToList(); - } - - static List FindStatementsToDelete( - IReadOnlyList remote, - HashSet localSet, - bool reconcile) - { - if (!reconcile) - { - return new List(); - } - - var toDelete = remote - .Where(r => !localSet.Contains(r.Sid)) - .ToList(); - - return toDelete; - } - - static List FindFilesToDeploy( - IReadOnlyList files, - IReadOnlyList toDeploy) - { - return files - .Where(file => file.Statements.Any(toDeploy.Contains)) - .ToList(); - } - - static List FindFailedFiles( - List deploymentExceptions) - { - var failed = new List(); - - foreach (var exception in deploymentExceptions) - { - failed.AddRange(exception.AffectedFiles); - } - - return failed.Distinct().ToList(); - } - - async Task> GetServerProjectAccessPolicies( - IReadOnlyList configFiles, - bool dryRun) - { - try - { - return await m_ProjectAccessClient.GetAsync(); - } - catch (Exception e) - { - if (!dryRun) - SetFailedStatus(configFiles, detail: e.Message); - throw; - } - } - - void HandleDeploymentException(ICollection deploymentExceptions) - { - if (!deploymentExceptions.Any()) - { - return; - } - - foreach (var deploymentException in deploymentExceptions) - { - SetFailedStatus( - deploymentException.AffectedFiles, - deploymentException.StatusDescription, - deploymentException.StatusDetail); - } - } - - void SetFailedStatus(IReadOnlyList files, string status = null, string detail = null) - { - foreach (var projectAccessFile in files) - { - projectAccessFile.Status = new DeploymentStatus(status, detail, SeverityLevel.Error); - } - - SetStatusAndProgress( - files, - status ?? "Failed to deploy", - detail ?? " Unknown Error", - SeverityLevel.Error, - 0f); - } - - void SetStatusAndProgress( - IReadOnlyList files, - string status, - string detail, - SeverityLevel severityLevel, - float progress) - { - foreach (var file in files) - { - UpdateStatus( - file, - status, - detail, - severityLevel); - UpdateProgress(file, progress); - } - } - - protected virtual void UpdateStatus( - IProjectAccessFile projectAccessFile, - string status, - string detail, - SeverityLevel severityLevel) - { - projectAccessFile - .Statements - .ForEach(s => s.Status = new DeploymentStatus(status, detail, severityLevel)); - } - - protected virtual void UpdateProgress( - IProjectAccessFile projectAccessFile, - float progress) - { - projectAccessFile - .Statements - .ForEach(s => s.Progress = progress); - } - - - void SetStartDeployingStatus(IReadOnlyList files) - { - SetStatusAndProgress( - files, - string.Empty, - string.Empty, - SeverityLevel.None, - 0f); - } - - static void SetStatus( - IReadOnlyList items, - string status, - string detail, - SeverityLevel severityLevel) - { - foreach (var file in items) - { - file.Status = new DeploymentStatus(status, detail, severityLevel); - } - } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/ErrorHandling/DuplicateAuthoringStatementsException.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/ErrorHandling/DuplicateAuthoringStatementsException.cs deleted file mode 100644 index 3fa8c8c..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/ErrorHandling/DuplicateAuthoringStatementsException.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; -using System.Text; -using Unity.Services.Access.Authoring.Core.Model; - -namespace Unity.Services.Access.Authoring.Core.ErrorHandling -{ - [Serializable] - public class DuplicateAuthoringStatementsException : ProjectAccessPolicyDeploymentException - { - readonly string m_Sid; - - public override string Message => $"{StatusDescription} {StatusDetail}"; - - public override string StatusDescription => "Duplicate Sid in files."; - public override StatusLevel Level => StatusLevel.Error; - - public override string StatusDetail - { - get - { - var builder = new StringBuilder(); - builder.Append($"Multiple resources with the same identifier '{m_Sid}' were found. "); - builder.Append("Only a single resource for a given identifier may be deployed/fetched at the same time. "); - builder.Append("Give all resources unique identifiers or deploy/fetch them separately to proceed.\n"); - - foreach (var file in AffectedFiles) - { - builder.Append($" '{file.Path}'"); - } - - return builder.ToString(); - } - } - - public DuplicateAuthoringStatementsException(string sid, IReadOnlyList files) - { - m_Sid = sid; - AffectedFiles = new List(files); - } - - protected DuplicateAuthoringStatementsException(SerializationInfo info, StreamingContext context) - : base(info, context) - { } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/ErrorHandling/InvalidDataException.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/ErrorHandling/InvalidDataException.cs deleted file mode 100644 index 44b64f2..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/ErrorHandling/InvalidDataException.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; -using Unity.Services.Access.Authoring.Core.Model; - -namespace Unity.Services.Access.Authoring.Core.ErrorHandling -{ - [Serializable] - public class InvalidDataException : ProjectAccessPolicyDeploymentException - { - readonly IProjectAccessFile m_File; - readonly string m_ErrorMessage; - - public override string Message => $"{StatusDescription} {StatusDetail}"; - - - public override string StatusDescription => "Invalid Data."; - - public override string StatusDetail => $"The file {m_File.Name} contains Invalid Data: {m_ErrorMessage}"; - public override StatusLevel Level => StatusLevel.Error; - - public InvalidDataException(IProjectAccessFile file, string errorMessage) - { - m_File = file; - m_ErrorMessage = errorMessage; - AffectedFiles = new List {file}; - } - - protected InvalidDataException(SerializationInfo info, StreamingContext context) - : base(info, context) - { } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/ErrorHandling/ProjectAccessPolicyDeploymentException.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/ErrorHandling/ProjectAccessPolicyDeploymentException.cs deleted file mode 100644 index 89737c1..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/ErrorHandling/ProjectAccessPolicyDeploymentException.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; -using Unity.Services.Access.Authoring.Core.Model; - -namespace Unity.Services.Access.Authoring.Core.ErrorHandling -{ - public abstract class ProjectAccessPolicyDeploymentException : Exception - { - public List AffectedFiles { get; protected set; } - public abstract string StatusDescription { get; } - public abstract string StatusDetail { get; } - public abstract StatusLevel Level { get; } - - public enum StatusLevel - { - Error, - Warning - } - protected ProjectAccessPolicyDeploymentException() - { } - - protected ProjectAccessPolicyDeploymentException(SerializationInfo info, StreamingContext context) - : base(info, context) - { } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Fetch/ProjectAccessFetchHandler.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Fetch/ProjectAccessFetchHandler.cs deleted file mode 100644 index 23da944..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Fetch/ProjectAccessFetchHandler.cs +++ /dev/null @@ -1,260 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Unity.Services.Access.Authoring.Core.ErrorHandling; -using Unity.Services.Access.Authoring.Core.IO; -using Unity.Services.Access.Authoring.Core.Json; -using Unity.Services.Access.Authoring.Core.Model; -using Unity.Services.Access.Authoring.Core.Results; -using Unity.Services.Access.Authoring.Core.Service; -using Unity.Services.Access.Authoring.Core.Validations; -using Unity.Services.DeploymentApi.Editor; - -namespace Unity.Services.Access.Authoring.Core.Fetch -{ - public class ProjectAccessFetchHandler : IProjectAccessFetchHandler - { - internal const string FetchResultName = "project-statements.ac"; - readonly IProjectAccessClient m_Client; - readonly IFileSystem m_FileSystem; - readonly IJsonConverter m_JsonConverter; - readonly IProjectAccessConfigValidator m_ProjectAccessValidator; - - public ProjectAccessFetchHandler( - IProjectAccessClient client, - IFileSystem fileSystem, - IJsonConverter jsonConverter, - IProjectAccessConfigValidator projectAccessValidator) - { - m_Client = client; - m_FileSystem = fileSystem; - m_JsonConverter = jsonConverter; - m_ProjectAccessValidator = projectAccessValidator; - } - - public async Task FetchAsync( - string rootDirectory, - IReadOnlyList files, - bool dryRun = false, - bool reconcile = false, - CancellationToken token = default) - { - var remote = await m_Client.GetAsync(); - - var fetchExceptions = new List(); - - var validLocal = - m_ProjectAccessValidator.FilterNonDuplicatedAuthoringStatements(files, fetchExceptions); - - if (fetchExceptions.Count > 0) - { - await HandleFetchExceptions(fetchExceptions); - } - - var localSet = validLocal.Select(l => l.Sid).ToHashSet(); - var remoteSet = remote.Select(l => l.Sid).ToHashSet(); - - var toUpdate = FindEntriesToUpdate(remote, localSet); - var toDelete = FindEntriesToDelete(remoteSet, validLocal); - var toCreate = FindEntriesToCreate(remote, localSet, reconcile); - var toFetch = files.ToList(); - - if (dryRun) - { - var res = new FetchResult( - toCreate, - toUpdate, - toDelete, - toFetch); - UpdateStatus(toFetch, toDelete, toUpdate, toCreate); - return res; - } - - UpdateLocal(files, toUpdate); - DeleteLocal(files, toDelete); - - var defaultFile = GetDefaultFile(rootDirectory, toCreate, files); - - await WriteOrDeleteFiles(files); - - if (reconcile && toCreate.Count > 0) - { - await WriteOrDeleteFiles( - new[] - { - defaultFile - }); - - if (!toFetch.Contains(defaultFile)) - toFetch.Add(defaultFile); - } - - UpdateStatus(toFetch, toDelete, toUpdate, toCreate); - foreach (var file in toFetch) - { - file.Status = new DeploymentStatus("Deployed", string.Empty, SeverityLevel.Success); - } - return new FetchResult( - toCreate, - toUpdate, - toDelete, - toFetch); - } - - static IProjectAccessFile GetDefaultFile( - string rootDirectory, - IReadOnlyList toCreate, - IReadOnlyList files) - { - var defaultFile = files.FirstOrDefault(f => f.Name == FetchResultName); - - if (defaultFile == null) - { - var filePath = Path.GetFullPath(Path.Combine(rootDirectory, FetchResultName)); - var file = new ProjectAccessFile() - { - Name = Path.GetFileName(filePath), - Path = filePath, - }; - - foreach (var statement in toCreate) - { - statement.Path = filePath; - } - file.Statements = (List)toCreate; - - defaultFile = file; - } - else - { - defaultFile.UpdateOrCreateStatements(toCreate); - } - - return defaultFile; - } - - static List FindEntriesToUpdate( - IReadOnlyList remote, - HashSet localSet) - { - var toUpdate = remote - .Where(r => localSet.Contains(r.Sid)) - .ToList(); - - return toUpdate; - } - - static List FindEntriesToDelete( - HashSet remote, - IReadOnlyList local) - { - var toDelete = local - .Where(l => !remote.Contains(l.Sid)) - .ToList(); - - return toDelete; - } - - static List FindEntriesToCreate( - IReadOnlyList remote, - HashSet localSet, - bool reconcile) - { - if (!reconcile) - return new List(); - - return remote - .Where(k => !localSet.Contains(k.Sid)) - .ToList(); - } - - static void UpdateLocal( - IReadOnlyList files, - IReadOnlyList remote) - { - foreach (var file in files) - { - file.UpdateStatements(remote); - } - } - - static void DeleteLocal( - IReadOnlyList files, - IReadOnlyList toDelete) - { - foreach (var file in files.Where(file => file.Statements.Any())) - { - file.RemoveStatements(toDelete); - } - } - - async Task WriteOrDeleteFiles( - IReadOnlyList files) - { - var tasks = new List(files.Count); - - foreach (var file in files) - { - if (file.Statements.Any()) - { - var content = new ProjectAccessFileContent(file.Statements); - - var text = m_JsonConverter.SerializeObject(content); - - tasks.Add(m_FileSystem.WriteAllText(file.Path, text)); - } - else - { - tasks.Add(m_FileSystem.Delete(file.Path)); - } - } - - await Task.WhenAll(tasks); - } - - static Task HandleFetchExceptions(List fetchExceptions) - { - var exceptions = fetchExceptions - .SelectMany(exception => exception.AffectedFiles.SelectMany(file => file.Statements), - (exception, s) => new DuplicateAuthoringStatementsException(s.Sid, exception.AffectedFiles)) - .ToList(); - - if (exceptions.Count > 0) - { - throw new AggregateException(exceptions); - } - - return Task.CompletedTask; - } - - static void UpdateStatus( - IReadOnlyList files, - IReadOnlyList toDelete, - IReadOnlyList toUpdate, - IReadOnlyList toCreate) - { - var allStatements = files.SelectMany(s => s.Statements).ToList(); - var updateIds = toUpdate.Select(s => s.Sid).ToHashSet(); - var createIds = toCreate.Select(s => s.Sid).ToHashSet(); - var deleteIds = toDelete.Select(s => s.Sid).ToHashSet(); - //Must update the local references, not the remote ones - SetStatus(allStatements.Where(s => updateIds.Contains(s.Sid)).ToList(), "Updated"); - SetStatus(allStatements.Where(s => createIds.Contains(s.Sid)).ToList(), "Created"); - SetStatus(allStatements.Where(s => deleteIds.Contains(s.Sid)).ToList(), "Deleted"); - } - - static void SetStatus(List statements, string action) - { - statements.ForEach(s => s.Status = new DeploymentStatus("Fetched", action, SeverityLevel.Success)); - } - - static void SetStatus(IReadOnlyList statements, string message, string detail, SeverityLevel level) - { - foreach (var statement in statements) - statement.Status = new DeploymentStatus(message, detail, level); - } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Json/IJsonConverter.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Json/IJsonConverter.cs deleted file mode 100644 index 956f744..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Json/IJsonConverter.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Unity.Services.Access.Authoring.Core.Json -{ - public interface IJsonConverter - { - T DeserializeObject(string value, bool matchCamelCaseFieldName = false); - string SerializeObject(T obj); - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/AccessControlStatement.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/AccessControlStatement.cs deleted file mode 100644 index 7ddb2c2..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/AccessControlStatement.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Runtime.Serialization; -using Unity.Services.DeploymentApi.Editor; - -namespace Unity.Services.Access.Authoring.Core.Model -{ - [Serializable] - [DataContract(Name = "Statement")] - public class AccessControlStatement : IAcessControlStatement - { - public string Type => "Project Access Control Statement"; - - public AccessControlStatement() { } - [DataMember(Name = "Sid", IsRequired = true, EmitDefaultValue = true)] - public string Sid { get; set; } - - [DataMember(Name = "Action", IsRequired = true, EmitDefaultValue = true)] - public List Action { get; set; } - - [DataMember(Name = "Effect", IsRequired = true, EmitDefaultValue = true)] - public string Effect { get; set; } - - [DataMember(Name = "Principal", IsRequired = true, EmitDefaultValue = true)] - public string Principal { get; set; } - - [DataMember(Name = "Resource", IsRequired = true, EmitDefaultValue = true)] - public string Resource { get; set; } - - [DataMember(Name = "ExpiresAt", EmitDefaultValue = false)] - public DateTime ExpiresAt { get; set; } - - [DataMember(Name = "Version", EmitDefaultValue = false)] - public string Version { get; set; } - - float m_Progress; - DeploymentStatus m_Status; - - public string Name { get; set; } - public string Path { get; set; } - - public float Progress - { - get => m_Progress; - set => SetField(ref m_Progress, value); - } - - public DeploymentStatus Status - { - get => m_Status; - set => SetField(ref m_Status, value); - } - - public ObservableCollection States { get; } - - public override string ToString() - { - return $"'{Sid}' in '{Path}'"; - } - - public event PropertyChangedEventHandler PropertyChanged; - - protected void SetField( - ref T field, - T value, - Action onFieldChanged = null, - [CallerMemberName] string propertyName = null) - { - if (EqualityComparer.Default.Equals(field, value)) - return; - field = value; - OnPropertyChanged(propertyName!); - onFieldChanged?.Invoke(field); - } - - void OnPropertyChanged([CallerMemberName] string propertyName = "") - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - public bool HasStatementChanged(IReadOnlyList referenceList) - { - var reference = referenceList.SingleOrDefault(r => r.Sid == Sid); - - return reference != null && (reference.Effect != Effect || reference.Principal != Principal || - reference.Resource != Resource || reference.Version != Version || - reference.ExpiresAt != ExpiresAt || - HasStatementActionChanged(reference.Action, Action)); - } - - static bool HasStatementActionChanged(List remoteAction, List localAction) - { - var orderedRemoteAction = remoteAction.OrderByDescending(s => s).ToList(); - var orderedLocalAction = localAction.OrderByDescending(s => s).ToList(); - - return orderedRemoteAction.Count != orderedLocalAction.Count - || !orderedLocalAction.SequenceEqual(orderedRemoteAction); - } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IAcessControlStatement.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IAcessControlStatement.cs deleted file mode 100644 index b86e4d0..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IAcessControlStatement.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using Unity.Services.DeploymentApi.Editor; - -namespace Unity.Services.Access.Authoring.Core.Model -{ - public interface IAcessControlStatement : IDeploymentItem, ITypedItem - { - string Sid { get; set; } - List Action { get; set; } - string Effect { get; set; } - string Principal { get; set; } - string Resource { get; set; } - DateTime ExpiresAt { get; set; } - string Version { get; set; } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IProjectAccessFile.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IProjectAccessFile.cs deleted file mode 100644 index 49935d0..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IProjectAccessFile.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using Unity.Services.DeploymentApi.Editor; - -namespace Unity.Services.Access.Authoring.Core.Model -{ - public interface IProjectAccessFile : IDeploymentItem - { - List Statements { get; set; } - - new float Progress { get; set; } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IProjectAccessMerger.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IProjectAccessMerger.cs deleted file mode 100644 index ca59de2..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IProjectAccessMerger.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; - -namespace Unity.Services.Access.Authoring.Core.Model -{ - public interface IProjectAccessMerger - { - List MergeStatementsToDeploy( - IReadOnlyList toCreate, - IReadOnlyList toUpdate, - IReadOnlyList toDelete, - IReadOnlyList remoteStatements); - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IProjectAccessParser.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IProjectAccessParser.cs deleted file mode 100644 index 4678d1f..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/IProjectAccessParser.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Unity.Services.Access.Authoring.Core.Model -{ - public interface IProjectAccessParser - { - List ParseFile(ProjectAccessFileContent content, IProjectAccessFile file); - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessFile.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessFile.cs deleted file mode 100644 index 07b040e..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessFile.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using Unity.Services.DeploymentApi.Editor; - -namespace Unity.Services.Access.Authoring.Core.Model -{ - [Serializable] - public class ProjectAccessFile : DeploymentItem, IProjectAccessFile - { - public ProjectAccessFile() - { - Type = "Project Access File"; - } - - public sealed override string Path - { - get => base.Path; - set - { - base.Path = value; - Name = System.IO.Path.GetFileName(value); - } - } - - public List Statements { get; set; } - - public override string ToString() - { - return $"'{Path}'"; - } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessFileContent.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessFileContent.cs deleted file mode 100644 index 9b66107..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessFileContent.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Unity.Services.Access.Authoring.Core.Model -{ - public class ProjectAccessFileContent - { - public readonly IReadOnlyList Statements; - - public ProjectAccessFileContent() - { - Statements = new List(); - } - public ProjectAccessFileContent(IReadOnlyList statements) - { - Statements = new List(statements); - } - - public List ToAuthoringStatements(IProjectAccessFile file, IProjectAccessParser parser) - { - return parser.ParseFile(this, file).ToList(); - } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessFileExtension.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessFileExtension.cs deleted file mode 100644 index 5df6390..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessFileExtension.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Unity.Services.Access.Authoring.Core.Model -{ - public static class ProjectAccessFileExtension - { - public static ProjectAccessFileContent ToFileContent(this IProjectAccessFile file) - { - return new ProjectAccessFileContent(file.Statements); - } - - public static void RemoveStatements(this IProjectAccessFile file, IReadOnlyList statementsToRemove) - { - foreach (var statementToRemove in statementsToRemove) - { - var index = file.Statements.FindIndex(statement => statement.Sid == statementToRemove.Sid); - - if (index >= 0) - { - statementToRemove.Path = file.Path; - statementToRemove.Name = statementToRemove.Sid; - } - } - - file.Statements.RemoveAll(statement => statementsToRemove.Any(statementToRemove => statement.Sid == statementToRemove.Sid)); - } - - public static void UpdateStatements(this IProjectAccessFile file, IReadOnlyList statementsToUpdate) - { - foreach (var statementToUpdate in statementsToUpdate) - { - var index = file.Statements.FindIndex(statement => statement.Sid == statementToUpdate.Sid); - - if (index >= 0) - { - file.Statements[index] = statementToUpdate; - statementToUpdate.Path = file.Path; - statementToUpdate.Name = statementToUpdate.Sid; - } - } - } - - public static void UpdateOrCreateStatements(this IProjectAccessFile file, IReadOnlyList statementsToCreateOrUpdate) - { - foreach (var statementToCreateOrUpdate in statementsToCreateOrUpdate) - { - var index = file.Statements.FindIndex(statement => statement.Sid == statementToCreateOrUpdate.Sid); - statementToCreateOrUpdate.Path = file.Path; - statementToCreateOrUpdate.Name = statementToCreateOrUpdate.Sid; - - if (index >= 0) - { - file.Statements[index] = statementToCreateOrUpdate; - } - else - { - file.Statements.Add(statementToCreateOrUpdate); - } - } - } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessMerger.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessMerger.cs deleted file mode 100644 index da8bfc7..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessMerger.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Unity.Services.Access.Authoring.Core.Model -{ - public class ProjectAccessMerger: IProjectAccessMerger - { - public List MergeStatementsToDeploy( - IReadOnlyList toCreate, - IReadOnlyList toUpdate, - IReadOnlyList toDelete, - IReadOnlyList remoteStatements) - { - var localStatements = toCreate.Concat(toUpdate).ToList(); - var remoteStatementsExceptToDelete = remoteStatements.Except(toDelete).ToList(); - - return MergeStatements(localStatements, remoteStatementsExceptToDelete); - } - - static List MergeStatements( - IReadOnlyList localStatements, - IReadOnlyList remoteStatements) - { - var localStatementsList = localStatements.ToList(); - - var localStatementSids = localStatementsList.Select(statement => statement.Sid).ToList(); - var remoteStatementSids = remoteStatements.Select(statement => statement.Sid).ToList(); - - var conflicts = localStatementSids.Intersect(remoteStatementSids); - var cleanedUpStatementsFromRemote = - remoteStatements.Where(statement => !conflicts.Contains(statement.Sid)); - - var finalStatementList = new List(); - - finalStatementList.AddRange(localStatementsList); - finalStatementList.AddRange(cleanedUpStatementsFromRemote); - - return finalStatementList; - } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessParser.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessParser.cs deleted file mode 100644 index a31b5ed..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Model/ProjectAccessParser.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace Unity.Services.Access.Authoring.Core.Model -{ - public class ProjectAccessParser : IProjectAccessParser - { - public List ParseFile(ProjectAccessFileContent content, IProjectAccessFile file) - { - var authoringStatements = new List(); - foreach (var statement in content.Statements) - { - statement.Path = file.Path; - statement.Name = statement.Sid; - authoringStatements.Add(statement); - } - - return authoringStatements; - } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Results/DeployResult.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Results/DeployResult.cs deleted file mode 100644 index 98df3df..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Results/DeployResult.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using Unity.Services.Access.Authoring.Core.Model; - -namespace Unity.Services.Access.Authoring.Core.Results -{ - public class DeployResult - { - public IReadOnlyList Created { get; set; } - public IReadOnlyList Updated { get; set; } - public IReadOnlyList Deleted { get; set; } - public IReadOnlyList Deployed { get; set; } - public IReadOnlyList Failed { get; set; } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Results/FetchResult.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Results/FetchResult.cs deleted file mode 100644 index 6511837..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Results/FetchResult.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using Unity.Services.Access.Authoring.Core.Model; - -namespace Unity.Services.Access.Authoring.Core.Results -{ - public class FetchResult : Result - { - public IReadOnlyList Fetched { get; } - - public FetchResult( - IReadOnlyList created, - IReadOnlyList updated, - IReadOnlyList deleted, - IReadOnlyList fetched = null, - IReadOnlyList failed = null) : base(created, updated,deleted) - { - Fetched = fetched ?? Array.Empty(); - } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Results/Result.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Results/Result.cs deleted file mode 100644 index c996075..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Results/Result.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using Unity.Services.Access.Authoring.Core.Model; - -namespace Unity.Services.Access.Authoring.Core.Results -{ - public class Result - { - public IReadOnlyList Created { get; } - public IReadOnlyList Updated { get; } - public IReadOnlyList Deleted { get; } - - public IReadOnlyList Failed { get; } - - public Result( - IReadOnlyList created, - IReadOnlyList updated, - IReadOnlyList deleted, - IReadOnlyList failed = null) - { - Created = created; - Updated = updated; - Deleted = deleted; - Failed = failed ?? Array.Empty(); - } - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Service/IProjectAccessClient.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Service/IProjectAccessClient.cs deleted file mode 100644 index 07f478f..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Service/IProjectAccessClient.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Unity.Services.Access.Authoring.Core.Model; - -namespace Unity.Services.Access.Authoring.Core.Service -{ - public interface IProjectAccessClient - { - void Initialize(string environmentId, string projectId, CancellationToken cancellationToken); - - Task> GetAsync(); - Task UpsertAsync(IReadOnlyList authoringStatements); - Task DeleteAsync(IReadOnlyList authoringStatements); - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Validations/IProjectAccessConfigValidator.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Validations/IProjectAccessConfigValidator.cs deleted file mode 100644 index 0c50abe..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Validations/IProjectAccessConfigValidator.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using Unity.Services.Access.Authoring.Core.ErrorHandling; -using Unity.Services.Access.Authoring.Core.Model; - -namespace Unity.Services.Access.Authoring.Core.Validations -{ - public interface IProjectAccessConfigValidator - { - List FilterNonDuplicatedAuthoringStatements( - IReadOnlyList files, - ICollection deploymentExceptions); - - bool Validate( - IProjectAccessFile file, - ICollection deploymentExceptions); - } -} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Validations/ProjectAccessConfigValidator.cs b/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Validations/ProjectAccessConfigValidator.cs deleted file mode 100644 index 1d5a69f..0000000 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Validations/ProjectAccessConfigValidator.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using Unity.Services.Access.Authoring.Core.ErrorHandling; -using Unity.Services.Access.Authoring.Core.Model; -using Unity.Services.DeploymentApi.Editor; - -namespace Unity.Services.Access.Authoring.Core.Validations -{ - public class ProjectAccessConfigValidator : IProjectAccessConfigValidator - { - public List FilterNonDuplicatedAuthoringStatements( - IReadOnlyList files, - ICollection deploymentExceptions) - { - var nonDuplicatedStatements = new List(); - - foreach (var f in files) - { - ProjectAccessFile file = (ProjectAccessFile)f; - - foreach (var statement in file.Statements) - { - - var containingFiles = files - .Where(f => f.Statements.Exists(fs => fs.Sid == statement.Sid)) - .ToList(); - - if (containingFiles.Count > 1) - { - deploymentExceptions.Add(new DuplicateAuthoringStatementsException(statement.Sid, containingFiles)); - file.Status = new DeploymentStatus("Validation Error", $"Multiple resources with the same identifier '{statement.Sid}' were found. ", SeverityLevel.Error); - continue; - } - - var duplicatedStatementsInASameFileCount = file.Statements - .GroupBy(s => s.Sid).Count(t => t.Count() > 1); - - if (duplicatedStatementsInASameFileCount > 0) - { - containingFiles.Add(file); - deploymentExceptions.Add(new DuplicateAuthoringStatementsException(statement.Sid, containingFiles)); - file.Status = new DeploymentStatus("Validation Error", $"Multiple resources with the same identifier '{statement.Sid}' were found. ", SeverityLevel.Error); - continue; - } - - nonDuplicatedStatements.Add(statement); - } - - } - - return nonDuplicatedStatements; - } - - public bool Validate( - IProjectAccessFile file, - ICollection deploymentExceptions) - { - bool validated = true; - - foreach (var authoringStatement in file.Statements) - { - var isSidValidated = ValidateSid(authoringStatement.Sid, (ProjectAccessFile)file, deploymentExceptions); - var isResourceValidated = ValidateResource(authoringStatement.Resource, (ProjectAccessFile)file, deploymentExceptions); - var isActionValidated = ValidateAction(authoringStatement.Action, (ProjectAccessFile)file, deploymentExceptions); - var isPrincipalValidated = ValidatePrincipal(authoringStatement.Principal, (ProjectAccessFile)file, deploymentExceptions); - var isEffectValidated = ValidateEffect(authoringStatement.Effect, (ProjectAccessFile)file, deploymentExceptions); - - if (!isSidValidated || !isResourceValidated || !isActionValidated || !isPrincipalValidated || !isEffectValidated) - { - validated = false; - } - } - - return validated; - } - - static bool ValidateSid(string sid, ProjectAccessFile projectAccessFile, ICollection deploymentExceptions) - { - Regex regex = new Regex("^[A-Za-z0-9][A-Za-z0-9_-]{5,59}$", RegexOptions.CultureInvariant, matchTimeout: TimeSpan.FromSeconds(2)); - if (regex.Match(sid).Success) return true; - - deploymentExceptions.Add(new InvalidDataException(projectAccessFile, "Invalid value for Sid, must match a pattern of " + regex)); - projectAccessFile.Status = new DeploymentStatus("Validation Error", "Invalid value for Sid, must match a pattern of " + regex, SeverityLevel.Error); - return false; - - } - - static bool ValidateResource(string resource, ProjectAccessFile projectAccessFile, ICollection deploymentExceptions) - { - Regex regex = new Regex("^urn:ugs:(([a-z-]*:){1}[*/]*[/a-z0-9-*]*|\\*{1})", RegexOptions.CultureInvariant, matchTimeout: TimeSpan.FromSeconds(2)); - if (regex.Match(resource).Success) return true; - - deploymentExceptions.Add(new InvalidDataException(projectAccessFile, "Invalid value for Resource, must match a pattern of " + regex)); - projectAccessFile.Status = new DeploymentStatus("Validation Error", "Invalid value for Resource, must match a pattern of " + regex, SeverityLevel.Error); - return false; - } - - static bool ValidateAction(List action, ProjectAccessFile projectAccessFile, ICollection deploymentExceptions) - { - var validActions = new List{ "*", "Read", "Write", "Vivox:JoinMuted", "Vivox:JoinAllMuted" }; - var invalidActions = action.Where(v => !validActions.Contains(v)); - if (!invalidActions.Any()) return true; - - deploymentExceptions.Add(new InvalidDataException(projectAccessFile, "Invalid Value for Action, must be '*', 'Read', 'Write', 'Vivox:JoinMuted' or 'Vivox:JoinAllMuted'")); - projectAccessFile.Status = new DeploymentStatus("Validation Error", "Invalid Value for Action, must be '*', 'Read', 'Write', 'Vivox:JoinMuted' or 'Vivox:JoinAllMuted'", SeverityLevel.Error); - return false; - } - - static bool ValidatePrincipal(string principal, ProjectAccessFile projectAccessFile, ICollection deploymentExceptions) - { - var validPrincipals = new List{ "Player" }; - if (validPrincipals.Contains(principal)) return true; - - deploymentExceptions.Add(new InvalidDataException(projectAccessFile, "Invalid Value for Principal, must be 'Player'")); - projectAccessFile.Status = new DeploymentStatus("Validation Error", "Invalid Value for Principal, must be 'Player'", SeverityLevel.Error); - return false; - } - - static bool ValidateEffect(string effect, ProjectAccessFile projectAccessFile, ICollection deploymentExceptions) - { - var validEffects = new List{ "Allow", "Deny" }; - if (validEffects.Contains(effect)) return true; - - deploymentExceptions.Add(new InvalidDataException(projectAccessFile, "Invalid Value for Effect, must be 'Allow' or 'Deny")); - projectAccessFile.Status = new DeploymentStatus("Validation Error", "Invalid Value for Effect, must be 'Allow' or 'Deny", SeverityLevel.Error); - return false; - } - - - } -} diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/AccessConfigLoaderTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/AccessConfigLoaderTests.cs index 0feeafb..68d7317 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/AccessConfigLoaderTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/AccessConfigLoaderTests.cs @@ -2,8 +2,8 @@ using Moq; using NUnit.Framework; using Unity.Services.Cli.Access.Deploy; -using Unity.Services.Access.Authoring.Core.Json; -using IFileSystem = Unity.Services.Access.Authoring.Core.IO.IFileSystem; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Json; +using IFileSystem = Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.IO.IFileSystem; namespace Unity.Services.Cli.Access.UnitTest.Deploy; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/AuthoringResultTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/AuthoringResultTests.cs index eba8ba7..7274fc9 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/AuthoringResultTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/AuthoringResultTests.cs @@ -1,6 +1,6 @@ using NUnit.Framework; -using Unity.Services.Access.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; using Unity.Services.Cli.Access.Deploy; using Unity.Services.DeploymentApi.Editor; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/JsonConverterTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/JsonConverterTests.cs index db06795..b9420a8 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/JsonConverterTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/JsonConverterTests.cs @@ -1,7 +1,7 @@ using NUnit.Framework; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -using Unity.Services.Access.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; using Unity.Services.Cli.Access.UnitTest.Utils; using JsonConverter = Unity.Services.Cli.Access.Deploy.JsonConverter; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessClientTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessClientTests.cs index e174d98..4cb8b4d 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessClientTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessClientTests.cs @@ -1,7 +1,7 @@ using Moq; using Newtonsoft.Json; using NUnit.Framework; -using Unity.Services.Access.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; using Unity.Services.Cli.Access.Deploy; using Unity.Services.Cli.Access.Service; using Unity.Services.Cli.Access.UnitTest.Utils; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessDeploymentHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessDeploymentHandlerTests.cs index e2b6e8f..a641950 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessDeploymentHandlerTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessDeploymentHandlerTests.cs @@ -1,11 +1,11 @@ using Moq; using NUnit.Framework; -using Unity.Services.Access.Authoring.Core.Deploy; -using Unity.Services.Access.Authoring.Core.Model; -using Unity.Services.Access.Authoring.Core.Service; -using Unity.Services.Access.Authoring.Core.Validations; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Deploy; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Service; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Validations; using Unity.Services.Cli.Access.UnitTest.Utils; -using InvalidDataException = Unity.Services.Access.Authoring.Core.ErrorHandling.InvalidDataException; +using InvalidDataException = Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.ErrorHandling.InvalidDataException; namespace Unity.Services.Cli.Access.UnitTest.Deploy; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessDeploymentServiceTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessDeploymentServiceTests.cs index e24296a..ddf7f64 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessDeploymentServiceTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessDeploymentServiceTests.cs @@ -1,9 +1,9 @@ using Moq; using NUnit.Framework; -using Unity.Services.Access.Authoring.Core.Deploy; -using Unity.Services.Access.Authoring.Core.Model; -using Unity.Services.Access.Authoring.Core.Results; -using Unity.Services.Access.Authoring.Core.Service; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Deploy; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Results; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Service; using Unity.Services.Cli.Access.Deploy; using Unity.Services.Cli.Access.UnitTest.Utils; using Unity.Services.Cli.Authoring.Input; @@ -66,7 +66,8 @@ public void SetUp() handler => handler.DeployAsync( It.IsAny>(), It.IsAny(), - It.IsAny() + It.IsAny(), + It.IsAny() )) .Returns(fromResult); } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessFetchHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessFetchHandlerTests.cs index d0034a3..a3fbb2e 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessFetchHandlerTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessFetchHandlerTests.cs @@ -1,11 +1,11 @@ using Moq; using NUnit.Framework; -using Unity.Services.Access.Authoring.Core.Fetch; -using Unity.Services.Access.Authoring.Core.IO; -using Unity.Services.Access.Authoring.Core.Json; -using Unity.Services.Access.Authoring.Core.Model; -using Unity.Services.Access.Authoring.Core.Service; -using Unity.Services.Access.Authoring.Core.Validations; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Fetch; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.IO; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Json; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Service; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Validations; using Unity.Services.Cli.Access.UnitTest.Utils; namespace Unity.Services.Cli.Access.UnitTest.Deploy; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessFetchServiceTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessFetchServiceTests.cs index b408f7a..43b059c 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessFetchServiceTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Deploy/ProjectAccessFetchServiceTests.cs @@ -1,9 +1,9 @@ using Moq; using NUnit.Framework; -using Unity.Services.Access.Authoring.Core.Fetch; -using Unity.Services.Access.Authoring.Core.Model; -using Unity.Services.Access.Authoring.Core.Results; -using Unity.Services.Access.Authoring.Core.Service; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Fetch; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Results; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Service; using Unity.Services.Cli.Access.Deploy; using Unity.Services.Cli.Access.UnitTest.Utils; using Unity.Services.Cli.Authoring.Input; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Model/ProjectAccessFileExtensionTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Model/ProjectAccessFileExtensionTests.cs index ced2690..58e50ed 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Model/ProjectAccessFileExtensionTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Model/ProjectAccessFileExtensionTests.cs @@ -1,5 +1,5 @@ using NUnit.Framework; -using Unity.Services.Access.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; namespace Unity.Services.Cli.Access.UnitTest.Model; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Unity.Services.Cli.Access.UnitTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Unity.Services.Cli.Access.UnitTest.csproj index 022f6f4..a2f6e79 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Unity.Services.Cli.Access.UnitTest.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Unity.Services.Cli.Access.UnitTest.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable enable false diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Utils/TestMocks.cs b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Utils/TestMocks.cs index 10e1608..7f65740 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Utils/TestMocks.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access.UnitTest/Utils/TestMocks.cs @@ -1,9 +1,9 @@ -using Unity.Services.Access.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; using Unity.Services.Gateway.AccessApiV1.Generated.Model; namespace Unity.Services.Cli.Access.UnitTest.Utils; -public class TestMocks +class TestMocks { public static Statement GetStatement(string sid = "statement-1") { diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access/AccessModule.cs b/Unity.Services.Cli/Unity.Services.Cli.Access/AccessModule.cs index ed168ed..e1461ae 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access/AccessModule.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access/AccessModule.cs @@ -12,13 +12,13 @@ using Unity.Services.Cli.Common.Utils; using Unity.Services.Gateway.AccessApiV1.Generated.Api; using Unity.Services.Gateway.AccessApiV1.Generated.Client; -using Unity.Services.Access.Authoring.Core.Deploy; -using Unity.Services.Access.Authoring.Core.Fetch; -using Unity.Services.Access.Authoring.Core.Json; -using Unity.Services.Access.Authoring.Core.Model; -using Unity.Services.Access.Authoring.Core.Validations; -using Unity.Services.Access.Authoring.Core.IO; -using Unity.Services.Access.Authoring.Core.Service; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Deploy; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Fetch; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Json; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Validations; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.IO; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Service; using Unity.Services.Cli.Access.Deploy; using Unity.Services.Cli.Access.IO; using Unity.Services.Cli.Access.Models; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/AccessAuthoringResult.cs b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/AccessAuthoringResult.cs index 4e0d21c..a23bdcd 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/AccessAuthoringResult.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/AccessAuthoringResult.cs @@ -1,4 +1,4 @@ -using Unity.Services.Access.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; using Unity.Services.Cli.Authoring.Model; using Unity.Services.Cli.Authoring.Model.TableOutput; using Unity.Services.DeploymentApi.Editor; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/AccessConfigLoader.cs b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/AccessConfigLoader.cs index 0bffc9c..32014a4 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/AccessConfigLoader.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/AccessConfigLoader.cs @@ -1,10 +1,10 @@ using Newtonsoft.Json; using System.IO.Abstractions; -using Unity.Services.Access.Authoring.Core.Json; -using Unity.Services.Access.Authoring.Core.Model; -using Unity.Services.Cli.Authoring.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Json; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; using Unity.Services.DeploymentApi.Editor; -using IFileSystem = Unity.Services.Access.Authoring.Core.IO.IFileSystem; +using IFileSystem = Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.IO.IFileSystem; +using Statuses = Unity.Services.Cli.Authoring.Model.Statuses; namespace Unity.Services.Cli.Access.Deploy; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/IAccessConfigLoader.cs b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/IAccessConfigLoader.cs index f5da896..f9a8bbd 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/IAccessConfigLoader.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/IAccessConfigLoader.cs @@ -1,6 +1,6 @@ namespace Unity.Services.Cli.Access.Deploy; -public interface IAccessConfigLoader +interface IAccessConfigLoader { Task LoadFilesAsync( IReadOnlyList filePaths, diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/JsonConverter.cs b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/JsonConverter.cs index 080dada..54d9527 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/JsonConverter.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/JsonConverter.cs @@ -1,7 +1,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; -using Unity.Services.Access.Authoring.Core.Json; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Json; namespace Unity.Services.Cli.Access.Deploy; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/LoadResult.cs b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/LoadResult.cs index 2df0dd1..8739df6 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/LoadResult.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/LoadResult.cs @@ -1,8 +1,8 @@ -using Unity.Services.Access.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; namespace Unity.Services.Cli.Access.Deploy; -public class LoadResult +class LoadResult { public IReadOnlyList Loaded { get; } public IReadOnlyList Failed { get; } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/ProjectAccessClient.cs b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/ProjectAccessClient.cs index d82f6ea..0d4b440 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/ProjectAccessClient.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/ProjectAccessClient.cs @@ -1,11 +1,11 @@ -using Unity.Services.Access.Authoring.Core.Model; -using Unity.Services.Access.Authoring.Core.Service; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Service; using Unity.Services.Cli.Access.Service; using Unity.Services.Gateway.AccessApiV1.Generated.Model; namespace Unity.Services.Cli.Access.Deploy; -public class ProjectAccessClient : IProjectAccessClient +class ProjectAccessClient : IProjectAccessClient { public string ProjectId { get; private set; } public string EnvironmentId { get; private set; } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/ProjectAccessDeploymentService.cs b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/ProjectAccessDeploymentService.cs index 7244939..590e29b 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/ProjectAccessDeploymentService.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/ProjectAccessDeploymentService.cs @@ -1,14 +1,14 @@ using Spectre.Console; -using Unity.Services.Access.Authoring.Core.Deploy; -using Unity.Services.Access.Authoring.Core.Model; -using Unity.Services.Access.Authoring.Core.Service; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Deploy; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Service; using Unity.Services.Cli.Authoring.Input; using Unity.Services.Cli.Authoring.Model; using Unity.Services.Cli.Authoring.Service; namespace Unity.Services.Cli.Access.Deploy; -public class ProjectAccessDeploymentService : IDeploymentService +class ProjectAccessDeploymentService : IDeploymentService { readonly string m_ServiceType; readonly string m_ServiceName; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/ProjectAccessFetchService.cs b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/ProjectAccessFetchService.cs index bedb54a..ab9676a 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/ProjectAccessFetchService.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access/Deploy/ProjectAccessFetchService.cs @@ -1,14 +1,14 @@ using Spectre.Console; -using Unity.Services.Access.Authoring.Core.Fetch; -using Unity.Services.Access.Authoring.Core.Model; -using Unity.Services.Access.Authoring.Core.Service; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Fetch; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Service; using Unity.Services.Cli.Authoring.Input; using Unity.Services.Cli.Authoring.Model; using Unity.Services.Cli.Authoring.Service; namespace Unity.Services.Cli.Access.Deploy; -public class ProjectAccessFetchService : IFetchService +class ProjectAccessFetchService : IFetchService { readonly IProjectAccessFetchHandler m_FetchHandler; readonly IProjectAccessClient m_ProjectAccessClient; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access/IO/FileSystem.cs b/Unity.Services.Cli/Unity.Services.Cli.Access/IO/FileSystem.cs index 1d31e6d..6910489 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access/IO/FileSystem.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access/IO/FileSystem.cs @@ -1,4 +1,4 @@ -using Unity.Services.Access.Authoring.Core.IO; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.IO; namespace Unity.Services.Cli.Access.IO; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access/Models/NewProjectAccessFile.cs b/Unity.Services.Cli/Unity.Services.Cli.Access/Models/NewProjectAccessFile.cs index 1bda5b8..b779569 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access/Models/NewProjectAccessFile.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Access/Models/NewProjectAccessFile.cs @@ -1,11 +1,11 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -using Unity.Services.Access.Authoring.Core.Model; +using Unity.Services.Tooling.Editor.AccessControl.Authoring.Core.Model; using Unity.Services.Cli.Authoring.Templates; namespace Unity.Services.Cli.Access.Models; -public class NewProjectAccessFile : IFileTemplate +class NewProjectAccessFile : IFileTemplate { [JsonProperty("$schema")] public string Value { get; set; } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Access/Unity.Services.Cli.Access.csproj b/Unity.Services.Cli/Unity.Services.Cli.Access/Unity.Services.Cli.Access.csproj index 528709b..fbce1b6 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Access/Unity.Services.Cli.Access.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Access/Unity.Services.Cli.Access.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 10 enable enable @@ -18,10 +18,10 @@ - + diff --git a/Unity.Services.Cli/Unity.Services.Cli.Authoring.UnitTest/Unity.Services.Cli.Authoring.UnitTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.Authoring.UnitTest/Unity.Services.Cli.Authoring.UnitTest.csproj index acedbc6..1e2e75c 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Authoring.UnitTest/Unity.Services.Cli.Authoring.UnitTest.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Authoring.UnitTest/Unity.Services.Cli.Authoring.UnitTest.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable enable true diff --git a/Unity.Services.Cli/Unity.Services.Cli.Authoring/Exceptions/DeployException.cs b/Unity.Services.Cli/Unity.Services.Cli.Authoring/Exceptions/DeployException.cs index f161ec4..9c63f10 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Authoring/Exceptions/DeployException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Authoring/Exceptions/DeployException.cs @@ -6,11 +6,8 @@ namespace Unity.Services.Cli.Authoring.Exceptions; /// /// Exception caused by user operation during deploy /// -[Serializable] public class DeployException : CliException { - protected DeployException(SerializationInfo info, StreamingContext context) : base(info, context) { } - public DeployException(int exitCode = Common.Exceptions.ExitCode.HandledError) : base(exitCode) { diff --git a/Unity.Services.Cli/Unity.Services.Cli.Authoring/Exceptions/InvalidExtensionException.cs b/Unity.Services.Cli/Unity.Services.Cli.Authoring/Exceptions/InvalidExtensionException.cs index 716c0dc..a4063e7 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Authoring/Exceptions/InvalidExtensionException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Authoring/Exceptions/InvalidExtensionException.cs @@ -5,11 +5,8 @@ namespace Unity.Services.Cli.Authoring.Exceptions; /// /// Exception when a user specified deploy path does not have expected extension /// -[Serializable] public class InvalidExtensionException: DeployException { public InvalidExtensionException(string path, string extension) : base($"File path must end in '{extension}': {path}") { } - - protected InvalidExtensionException(SerializationInfo info, StreamingContext context) : base(info, context) { } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Authoring/Exceptions/PathNotFoundException.cs b/Unity.Services.Cli/Unity.Services.Cli.Authoring/Exceptions/PathNotFoundException.cs index 57a12b6..cefe235 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Authoring/Exceptions/PathNotFoundException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Authoring/Exceptions/PathNotFoundException.cs @@ -5,11 +5,8 @@ namespace Unity.Services.Cli.Authoring.Exceptions; /// /// Exception when a user specified deploy path is not found /// -[Serializable] public class PathNotFoundException: DeployException { public PathNotFoundException(string path) : base($"Path {path} could not be found.") { } - - protected PathNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Authoring/Unity.Services.Cli.Authoring.csproj b/Unity.Services.Cli/Unity.Services.Cli.Authoring/Unity.Services.Cli.Authoring.csproj index bb9fb86..7574b48 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Authoring/Unity.Services.Cli.Authoring.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Authoring/Unity.Services.Cli.Authoring.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 10 enable enable @@ -19,8 +19,8 @@ - - + + $(DefineConstants);$(ExtraDefineConstants) diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudCode.UnitTest/Unity.Services.Cli.CloudCode.UnitTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.CloudCode.UnitTest/Unity.Services.Cli.CloudCode.UnitTest.csproj index 60d7a71..1057f74 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.CloudCode.UnitTest/Unity.Services.Cli.CloudCode.UnitTest.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudCode.UnitTest/Unity.Services.Cli.CloudCode.UnitTest.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable true false diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Exceptions/ScriptEvaluationException.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Exceptions/ScriptEvaluationException.cs index 466fafa..24da4a4 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Exceptions/ScriptEvaluationException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Exceptions/ScriptEvaluationException.cs @@ -4,14 +4,13 @@ namespace Unity.Services.Cli.CloudCode.Exceptions; -[Serializable] public class ScriptEvaluationException : CliException { - protected ScriptEvaluationException(SerializationInfo info, StreamingContext context) : base(info, context) { } - public ScriptEvaluationException(ProcessException exception) : base($"{exception.Message}", Common.Exceptions.ExitCode.HandledError) { } public ScriptEvaluationException(ArgumentOutOfRangeException exception) : base($"{exception.Message}", Common.Exceptions.ExitCode.HandledError) { } public ScriptEvaluationException(string message) : base($"{message}", Common.Exceptions.ExitCode.HandledError) { } + + public ScriptEvaluationException(string message, Exception inner) : base($"{message}", inner, Common.Exceptions.ExitCode.HandledError) { } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudCode/IO/FileSystem.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudCode/IO/FileSystem.cs index be77ee0..a0adb81 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.CloudCode/IO/FileSystem.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudCode/IO/FileSystem.cs @@ -96,4 +96,9 @@ public void FileMove(string sourceFileName, string destFileName) { File.Move(sourceFileName, destFileName); } + + public void MoveDirectory(string sourceDirName, string destDirName) + { + Directory.Move(sourceDirName, destDirName); + } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Parameters/CloudCodeScriptParser.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Parameters/CloudCodeScriptParser.cs index cf904af..9b464aa 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Parameters/CloudCodeScriptParser.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Parameters/CloudCodeScriptParser.cs @@ -81,7 +81,7 @@ public async Task ParseScriptParametersAsync(string var paramString = await m_CliProcess.ExecuteAsync("node", Directory.GetCurrentDirectory(), new[] { $"--max-old-space-size={k_MemorySizeLimitInMB}", - k_ParameterScriptFile + $"\"{k_ParameterScriptFile}\"", }, parseTimeoutTokenSource.Token, writeToStandardInput: WriteToProcessStandardInput); paramString = paramString.Replace(System.Environment.NewLine, ""); diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Parameters/CloudScriptParametersParser.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Parameters/CloudScriptParametersParser.cs index 2e7b11b..2d3de9a 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Parameters/CloudScriptParametersParser.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Parameters/CloudScriptParametersParser.cs @@ -7,6 +7,8 @@ namespace Unity.Services.Cli.CloudCode.Parameters; class CloudScriptParametersParser : ICloudScriptParametersParser { + static string ParamTypePossibleValues => string.Join(", ", Enum.GetNames(typeof(ScriptParameter.TypeEnum))); + struct EvaluatedParam { public ScriptParameter.TypeEnum Type = ScriptParameter.TypeEnum.ANY; @@ -61,11 +63,11 @@ static void ParseValue(JToken param, ScriptParameter result) } catch (JsonSerializationException ex) { - throw new ScriptEvaluationException(ex.Message); + throw new ScriptEvaluationException(ex.Message, ex); } catch (ArgumentException ex) { - throw new ScriptEvaluationException(ex.Message); + throw new ScriptEvaluationException($"Impossible to convert '{param}' to enum. Possible values are: {ParamTypePossibleValues}.", ex); } } @@ -79,6 +81,10 @@ static void ParseObject(JObject jParamData, ScriptParameter result) } catch (JsonSerializationException ex) { + if (ex.Path?.EndsWith(".type") ?? false) + { + throw new ScriptEvaluationException($"Impossible to convert '{jParamData.GetValue("type")}' to enum. Possible values are: {ParamTypePossibleValues}.", ex); + } throw new ScriptEvaluationException(ex.Message); } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Unity.Services.Cli.CloudCode.csproj b/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Unity.Services.Cli.CloudCode.csproj index a85ecfe..89c436b 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Unity.Services.Cli.CloudCode.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudCode/Unity.Services.Cli.CloudCode.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 10 enable enable @@ -21,7 +21,7 @@ - + diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/CloudContentDeliveryModuleTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/CloudContentDeliveryModuleTests.cs new file mode 100644 index 0000000..17cd2f1 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/CloudContentDeliveryModuleTests.cs @@ -0,0 +1,79 @@ +using System.CommandLine; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Moq; +using NUnit.Framework; +using Unity.Services.Cli.CloudContentDelivery; +using Unity.Services.Cli.Common.Networking; +using Unity.Services.Cli.ServiceAccountAuthentication; +using Unity.Services.CloudContentDelivery.Authoring.Core.IO; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Api; + +namespace CloudContentDeliveryTest; + +public class CloudContentDeliveryModuleTests +{ + static readonly CloudContentDeliveryModule k_Module = new(); + + [Test] + public void ValidateRootCommand() + { + Assert.That(k_Module.ModuleRootCommand, Is.Not.Null); + Assert.Multiple( + () => + { + Assert.That(k_Module.ModuleRootCommand?.Name, Is.EqualTo("ccd")); + Assert.That(k_Module.ModuleRootCommand?.Description, Is.EqualTo("Manage Cloud Content Delivery.")); + }); + } + + [Test] + public void ConfigureCloudContentDeliveryRegistersExpectedServices() + { + EndpointHelper.InitializeNetworkTargetEndpoints( + new[] + { + typeof(CloudContentDeliveryApiEndpoints).GetTypeInfo() + }); + + var collection = new ServiceCollection(); + collection.AddSingleton(ServiceDescriptor.Singleton(new Mock().Object)); + + CloudContentDeliveryModule.RegisterServices( + new HostBuilderContext(new Dictionary()), + collection); + Assert.Multiple( + () => + { + Assert.That(collection.Any(descriptor => descriptor.ServiceType == typeof(IFileSystem))); + Assert.That(collection.Any(descriptor => descriptor.ServiceType == typeof(IBadgesApi))); + Assert.That(collection.Any(descriptor => descriptor.ServiceType == typeof(IBucketsApi))); + Assert.That(collection.Any(descriptor => descriptor.ServiceType == typeof(IReleasesApi))); + Assert.That(collection.Any(descriptor => descriptor.ServiceType == typeof(IEntriesApi))); + Assert.That(collection.Any(descriptor => descriptor.ServiceType == typeof(IPermissionsApi))); + }); + } + + [Test] + public void CloudContentDeliveryRegisterModulesCommands() + { + var rootCommand = new Command("root", "Root Command"); + CloudContentDeliveryModule.RegisterModulesCommands(rootCommand); + + var commandNames = new[] + { + "buckets", + "entries", + "badges", + "releases" + }; + + foreach (var commandName in commandNames) + Assert.That( + rootCommand.Children.Any(subcommand => subcommand.Name == commandName), + Is.True, + $"Command '{commandName}' should exist."); + } + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/CloudContentDeliveryTestsConstants.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/CloudContentDeliveryTestsConstants.cs new file mode 100644 index 0000000..71dade8 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/CloudContentDeliveryTestsConstants.cs @@ -0,0 +1,56 @@ +namespace CloudContentDeliveryTest; + +public static class CloudContentDeliveryTestsConstants +{ + public const string ProjectId = "00000000-0000-0000-0000-000000000001"; + public const string BucketId = "00000000-0000-0000-0000-000000000002"; + public const string BucketName = "NewBucket"; + public const string TargetBucketName = "TargetBucket"; + + + public const string BadgeName = "NewBadge"; + public const string BucketDescription = "Description"; + public const string EnvironmentId = "00000000-0000-0000-0000-000000000003"; + public const string ReleaseId = "00000000-0000-0000-0000-000000000004"; + public const string EntryId = "00000000-0000-0000-0000-000000000005"; + public const string PromotionId = "00000000-0000-0000-0000-000000000006"; + public const string PromotedFromRelease = "00000000-0000-0000-0000-000000000007"; + public const string PromotedFromBucket = "00000000-0000-0000-0000-000000000008"; + + public const string ToBucket = "00000000-0000-0000-0000-000000000009"; + public const string ToEnvironment = "production"; + + public const int ReleaseNumber = 1; + public const string Notes = "my notes"; + public const string Metadata = ""; + public const bool PromoteOnly = true; + public const int Page = 1; + public const int PerPage = 10; + + public static readonly string StartingAfter = "00000000-0000-0000-0000-000000000011"; + public const string Path = "folder/file.jpg"; + + public static readonly string VersionId = "00000000-0000-0000-0000-000000000011"; + public const string? Label = "abc,def"; + public const string FilterName = ""; + + public static readonly List Labels = new() + { + "abc", + "def" + }; + + public const string LocalFolder = "folder"; + public const string ExclusionPattern = ""; + public const bool Delete = true; + public const int Retry = 3; + public const bool DryRun = false; + public const string UpdateBadge = "mybadge"; + public const bool CreateRelease = true; + + public const string ContentType = "image/jpeg"; + public const bool Complete = true; + public const string SortBy = "name"; + public const string SortOrder = "desc"; + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Badges/CreateBadgeHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Badges/CreateBadgeHandlerTests.cs new file mode 100644 index 0000000..4d77bf2 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Badges/CreateBadgeHandlerTests.cs @@ -0,0 +1,122 @@ +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Badges; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Utils; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace CloudContentDeliveryTest.Handlers.Badges; + +[TestFixture] +public class CreateBadgeHandlerTests +{ + readonly Mock m_MockBucketClient = new(); + [SetUp] + public void Setup() + { + m_MockBucketClient.Setup( + c => + c.GetBucketIdByName( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.BucketId); + } + + [Test] + public async Task CreateAsync_Success() + { + var input = new CloudContentDeliveryInput + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketNameOpt = CloudContentDeliveryTestsConstants.BucketName, + BadgeName = CloudContentDeliveryTestsConstants.BadgeName, + ReleaseId = CloudContentDeliveryTestsConstants.ReleaseId, + ReleaseNum = CloudContentDeliveryTestsConstants.ReleaseNumber + }; + + var unityEnvironment = new Mock(); + unityEnvironment.Setup(e => e.FetchIdentifierAsync(It.IsAny())) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + + + var badgeClient = new Mock(); + badgeClient.Setup( + s => + s.CreateBadgeAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.BadgeName, + CloudContentDeliveryTestsConstants.ReleaseNumber, + CancellationToken.None)) + .ReturnsAsync(new CcdGetBucket200ResponseLastReleaseBadgesInner()); + + var logger = new Mock(); + + await CreateBadgeHandler.CreateAsync( + input, + unityEnvironment.Object, + badgeClient.Object, + m_MockBucketClient.Object, + logger.Object, + CancellationToken.None); + + unityEnvironment.Verify(e => e.FetchIdentifierAsync(It.IsAny()), Times.Once); + badgeClient.Verify( + s => + s.CreateBadgeAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.BadgeName, + CloudContentDeliveryTestsConstants.ReleaseNumber, + It.IsAny()), + Times.Once); + } + + [Test] + public void CreateAsync_Exception() + { + + var input = new CloudContentDeliveryInput + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketNameOpt = CloudContentDeliveryTestsConstants.BucketName, + BadgeName = CloudContentDeliveryTestsConstants.BadgeName, + ReleaseId = "", + ReleaseNum = 0 + }; + var unityEnvironment = new Mock(); + unityEnvironment.Setup(e => e.FetchIdentifierAsync(It.IsAny())) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + var badgeClient = new Mock(); + badgeClient.Setup( + s => + s.CreateBadgeAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.BadgeName, + 0, + It.IsAny())) + .ThrowsAsync(new Exception("Failed to create badge")); + + var logger = new Mock(); + + Assert.ThrowsAsync( + () => + CreateBadgeHandler.CreateAsync( + input, + unityEnvironment.Object, + badgeClient.Object, + m_MockBucketClient.Object, + logger.Object, + CancellationToken.None)); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Badges/DeleteBadgeHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Badges/DeleteBadgeHandlerTests.cs new file mode 100644 index 0000000..f1306ad --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Badges/DeleteBadgeHandlerTests.cs @@ -0,0 +1,109 @@ +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Badges; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; + +namespace CloudContentDeliveryTest.Handlers.Badges; + +[TestFixture] +public class DeleteBadgeHandlerTests +{ + readonly Mock m_MockUnityEnvironment = new(); + readonly Mock m_MockBadgeClient = new(); + readonly Mock m_MockBucketClient = new(); + readonly Mock m_MockLogger = new(); + + [SetUp] + public void Setup() + { + m_MockUnityEnvironment.Reset(); + m_MockBadgeClient.Reset(); + m_MockLogger.Reset(); + + m_MockBucketClient.Setup( + c => + c.GetBucketIdByName( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.BucketId); + + + } + + [Test] + public async Task DeleteAsync_CallsLoadingIndicatorStartLoading() + { + var input = new CloudContentDeliveryInput(); + var mockLoadingIndicator = new Mock(); + var cancellationToken = CancellationToken.None; + + mockLoadingIndicator.Setup( + li => li.StartLoadingAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.CompletedTask); + + await DeleteBadgeHandler.DeleteAsync( + input, + m_MockUnityEnvironment.Object, + m_MockBadgeClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + mockLoadingIndicator.Object, + cancellationToken); + + mockLoadingIndicator.Verify( + ex => ex.StartLoadingAsync(It.IsAny(), It.IsAny>()), + Times.Once); + } + + [Test] + public async Task DeleteHandler_ValidInputLogsResult() + { + var cloudContentDeliveryInput = new CloudContentDeliveryInput + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketNameOpt = CloudContentDeliveryTestsConstants.BucketName, + BadgeName = CloudContentDeliveryTestsConstants.BadgeName + }; + var cancellationToken = CancellationToken.None; + + m_MockBadgeClient.Setup( + c => + c.DeleteBadgeAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.BadgeName, + CancellationToken.None)) + .ReturnsAsync("Badge deleted."); + + m_MockUnityEnvironment.Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + await DeleteBadgeHandler.DeleteAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockBadgeClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + cancellationToken); + + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(cancellationToken), Times.Once); + m_MockBadgeClient.Verify( + api => + api.DeleteBadgeAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.BadgeName, + cancellationToken), + Times.Once); + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Badges/ListBadgeHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Badges/ListBadgeHandlerTests.cs new file mode 100644 index 0000000..0d6263c --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Badges/ListBadgeHandlerTests.cs @@ -0,0 +1,129 @@ +using System.Net; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Badges; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace CloudContentDeliveryTest.Handlers.Badges; + +[TestFixture] +public class ListBadgeHandlerTests +{ + readonly Mock m_MockUnityEnvironment = new(); + readonly Mock m_MockBadgeClient = new(); + readonly Mock m_MockBucketClient = new(); + readonly Mock m_MockLogger = new(); + + [SetUp] + public void Setup() + { + m_MockUnityEnvironment.Reset(); + m_MockBadgeClient.Reset(); + m_MockLogger.Reset(); + + m_MockBucketClient.Setup( + c => + c.GetBucketIdByName( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.BucketId); + } + + [Test] + public async Task ListAsync_CallsLoadingIndicatorStartLoading() + { + var input = new CloudContentDeliveryInput(); + var mockLoadingIndicator = new Mock(); + var cancellationToken = CancellationToken.None; + + mockLoadingIndicator.Setup( + li => li.StartLoadingAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.CompletedTask); + + await ListBadgeHandler.ListAsync( + input, + m_MockUnityEnvironment.Object, + m_MockBadgeClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + mockLoadingIndicator.Object, + cancellationToken); + mockLoadingIndicator.Verify( + ex => ex.StartLoadingAsync(It.IsAny(), It.IsAny>()), + Times.Once); + } + + [Test] + public async Task ListHandler_ValidInputLogsResult() + { + var cloudContentDeliveryInput = new CloudContentDeliveryInput + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketNameOpt = CloudContentDeliveryTestsConstants.BucketName, + Page = CloudContentDeliveryTestsConstants.Page, + PerPage = CloudContentDeliveryTestsConstants.PerPage, + ReleaseNumOption = CloudContentDeliveryTestsConstants.ReleaseNumber.ToString(), + FilterName = CloudContentDeliveryTestsConstants.FilterName, + SortByBadge = CloudContentDeliveryTestsConstants.SortBy, + SortOrder = CloudContentDeliveryTestsConstants.SortOrder + }; + + var cancellationToken = CancellationToken.None; + var environmentId = CloudContentDeliveryTestsConstants.EnvironmentId; + + m_MockUnityEnvironment.Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(environmentId); + + ApiResponse> badgeListResult = new( + HttpStatusCode.OK, + new Multimap(), + new List()); + + m_MockBadgeClient.Setup( + api => api.ListBadgeAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Page, + CloudContentDeliveryTestsConstants.PerPage, + CloudContentDeliveryTestsConstants.FilterName, + CloudContentDeliveryTestsConstants.ReleaseNumber.ToString(), + CloudContentDeliveryTestsConstants.SortBy, + CloudContentDeliveryTestsConstants.SortOrder, + cancellationToken)) + .ReturnsAsync(badgeListResult); + + await ListBadgeHandler.ListAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockBadgeClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + cancellationToken); + + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(cancellationToken), Times.Once); + m_MockBadgeClient.Verify( + api => api.ListBadgeAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Page, + CloudContentDeliveryTestsConstants.PerPage, + CloudContentDeliveryTestsConstants.FilterName, + CloudContentDeliveryTestsConstants.ReleaseNumber.ToString(), + CloudContentDeliveryTestsConstants.SortBy, + CloudContentDeliveryTestsConstants.SortOrder, + cancellationToken), + Times.Once); + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/CreateBucketHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/CreateBucketHandlerTests.cs new file mode 100644 index 0000000..ee50a7b --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/CreateBucketHandlerTests.cs @@ -0,0 +1,100 @@ +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Buckets; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Utils; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace CloudContentDeliveryTest.Handlers.Buckets; + +[TestFixture] +public class CreateBucketHandlerTests +{ + [Test] + public async Task CreateAsync_Success() + { + var input = new CloudContentDeliveryInputBuckets + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketName = CloudContentDeliveryTestsConstants.BucketName, + BucketDescription = CloudContentDeliveryTestsConstants.BucketDescription, + BucketPrivate = true + }; + + var unityEnvironment = new Mock(); + unityEnvironment.Setup(e => e.FetchIdentifierAsync(It.IsAny())) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + var bucketClient = new Mock(); + bucketClient.Setup( + s => + s.CreateBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CloudContentDeliveryTestsConstants.BucketDescription, + true, + It.IsAny())) + .ReturnsAsync(new CcdGetBucket200Response()); + + var logger = new Mock(); + await CreateBucketHandler.CreateAsync( + input, + unityEnvironment.Object, + bucketClient.Object, + logger.Object, + CancellationToken.None); + unityEnvironment.Verify(e => e.FetchIdentifierAsync(It.IsAny()), Times.Once); + bucketClient.Verify( + s => + s.CreateBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CloudContentDeliveryTestsConstants.BucketDescription, + true, + It.IsAny()), + Times.Once); + } + + [Test] + public void CreateAsync_Exception() + { + + var input = new CloudContentDeliveryInputBuckets + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketName = CloudContentDeliveryTestsConstants.BucketName, + BucketDescription = CloudContentDeliveryTestsConstants.BucketDescription, + BucketPrivate = true + }; + var unityEnvironment = new Mock(); + unityEnvironment.Setup(e => e.FetchIdentifierAsync(It.IsAny())) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + var bucketClient = new Mock(); + bucketClient.Setup( + s => + s.CreateBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CloudContentDeliveryTestsConstants.BucketDescription, + true, + It.IsAny())) + .ThrowsAsync(new Exception("Failed to create bucket")); + + var logger = new Mock(); + + Assert.ThrowsAsync( + () => + CreateBucketHandler.CreateAsync( + input, + unityEnvironment.Object, + bucketClient.Object, + logger.Object, + CancellationToken.None)); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/DeleteBucketHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/DeleteBucketHandlerTests.cs new file mode 100644 index 0000000..9539253 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/DeleteBucketHandlerTests.cs @@ -0,0 +1,98 @@ +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Buckets; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; + +namespace CloudContentDeliveryTest.Handlers.Buckets; + +[TestFixture] +public class DeleteBucketHandlerTests +{ + readonly Mock m_MockUnityEnvironment = new(); + readonly Mock m_MockBucketClient = new(); + readonly Mock m_MockLogger = new(); + + [SetUp] + public void Setup() + { + m_MockUnityEnvironment.Reset(); + m_MockBucketClient.Reset(); + m_MockLogger.Reset(); + } + + [Test] + public async Task DeleteAsync_CallsLoadingIndicatorStartLoading() + { + var input = new CloudContentDeliveryInput(); + var mockLoadingIndicator = new Mock(); + var cancellationToken = CancellationToken.None; + mockLoadingIndicator.Setup( + li => li.StartLoadingAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.CompletedTask); + + await DeleteBucketHandler.DeleteAsync( + input, + m_MockUnityEnvironment.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + mockLoadingIndicator.Object, + cancellationToken); + mockLoadingIndicator.Verify( + ex => ex.StartLoadingAsync(It.IsAny(), It.IsAny>()), + Times.Once); + } + + [Test] + public async Task DeleteHandler_ValidInputLogsResult() + { + var cloudContentDeliveryInput = new CloudContentDeliveryInput + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketName = CloudContentDeliveryTestsConstants.BucketName + }; + var cancellationToken = CancellationToken.None; + + m_MockBucketClient.Setup( + c => + c.GetBucketIdByName( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.BucketId); + m_MockBucketClient.Setup( + c => + c.DeleteBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CancellationToken.None)) + .ReturnsAsync(new object()); + + m_MockUnityEnvironment.Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + await DeleteBucketHandler.DeleteAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + cancellationToken); + + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(cancellationToken), Times.Once); + m_MockBucketClient.Verify( + api => + api.DeleteBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + cancellationToken), + Times.Once); + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/GetBucketHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/GetBucketHandlerTests.cs new file mode 100644 index 0000000..325d3cd --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/GetBucketHandlerTests.cs @@ -0,0 +1,93 @@ +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Buckets; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace CloudContentDeliveryTest.Handlers.Buckets; + +[TestFixture] +public class GetBucketHandlerTests +{ + readonly Mock m_MockUnityEnvironment = new(); + readonly Mock m_MockBucketClient = new(); + readonly Mock m_MockLogger = new(); + + [SetUp] + public void SetUp() + { + m_MockUnityEnvironment.Reset(); + m_MockBucketClient.Reset(); + m_MockLogger.Reset(); + var result = new CcdGetBucket200Response(); + + m_MockBucketClient.Setup( + c => + c.GetBucketAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + CancellationToken.None)) + .ReturnsAsync(result); + + } + + [Test] + public async Task GetAsync_CallsLoadingIndicatorStartLoading() + { + + CloudContentDeliveryInput cloudContentDeliveryInput = new() + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketName = CloudContentDeliveryTestsConstants.BucketName + }; + + var mockLoadingIndicator = new Mock(); + await GetBucketHandler.GetAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + mockLoadingIndicator.Object, + CancellationToken.None); + mockLoadingIndicator.Verify( + ex => ex.StartLoadingAsync(It.IsAny(), It.IsAny>()), + Times.Once); + } + + [Test] + public async Task GetHandler_ValidInputLogsResult() + { + + CloudContentDeliveryInput cloudContentDeliveryInput = new() + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketName = CloudContentDeliveryTestsConstants.BucketName + }; + + m_MockUnityEnvironment.Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + await GetBucketHandler.GetAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + CancellationToken.None); + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(CancellationToken.None), Times.Once); + m_MockBucketClient.Verify( + api => + api.GetBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CancellationToken.None), + Times.Once); + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/ListBucketHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/ListBucketHandlerTests.cs new file mode 100644 index 0000000..7376f98 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/ListBucketHandlerTests.cs @@ -0,0 +1,115 @@ +using System.Net; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Buckets; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace CloudContentDeliveryTest.Handlers.Buckets; + +[TestFixture] +public class ListBucketHandlerTests +{ + readonly Mock m_MockUnityEnvironment = new(); + readonly Mock m_MockBucketClient = new(); + readonly Mock m_MockLogger = new(); + + [SetUp] + public void Setup() + { + m_MockUnityEnvironment.Reset(); + m_MockBucketClient.Reset(); + m_MockLogger.Reset(); + } + + [Test] + public async Task ListAsync_CallsLoadingIndicatorStartLoading() + { + var input = new CloudContentDeliveryInputBuckets(); + var mockLoadingIndicator = new Mock(); + var cancellationToken = CancellationToken.None; + + mockLoadingIndicator.Setup( + li => li.StartLoadingAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.CompletedTask); + + await ListBucketHandler.ListAsync( + input, + m_MockUnityEnvironment.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + mockLoadingIndicator.Object, + cancellationToken); + mockLoadingIndicator.Verify( + ex => ex.StartLoadingAsync(It.IsAny(), It.IsAny>()), + Times.Once); + } + + [Test] + public async Task ListHandler_ValidInputLogsResult() + { + var cloudContentDeliveryInput = new CloudContentDeliveryInputBuckets + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketName = CloudContentDeliveryTestsConstants.BucketName, + Page = CloudContentDeliveryTestsConstants.Page, + PerPage = CloudContentDeliveryTestsConstants.PerPage, + FilterName = CloudContentDeliveryTestsConstants.FilterName, + SortByBucket = CloudContentDeliveryTestsConstants.SortBy, + SortOrder = CloudContentDeliveryTestsConstants.SortOrder, + BucketDescription = CloudContentDeliveryTestsConstants.BucketDescription + + }; + var cancellationToken = CancellationToken.None; + + m_MockUnityEnvironment.Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + m_MockBucketClient.Setup( + c => + c.ListBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.Page, + CloudContentDeliveryTestsConstants.PerPage, + CloudContentDeliveryTestsConstants.FilterName, + CloudContentDeliveryTestsConstants.BucketDescription, + CloudContentDeliveryTestsConstants.SortBy, + CloudContentDeliveryTestsConstants.SortOrder, + CancellationToken.None)) + .ReturnsAsync( + new ApiResponse>( + HttpStatusCode.OK, + new Multimap(), + new List() + )); + + await ListBucketHandler.ListAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + cancellationToken); + + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(cancellationToken), Times.Once); + m_MockBucketClient.Verify( + api => + api.ListBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.Page, + CloudContentDeliveryTestsConstants.PerPage, + CloudContentDeliveryTestsConstants.FilterName, + CloudContentDeliveryTestsConstants.BucketDescription, + CloudContentDeliveryTestsConstants.SortBy, + CloudContentDeliveryTestsConstants.SortOrder, + cancellationToken), + Times.Once); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/PermissionBucketHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/PermissionBucketHandlerTests.cs new file mode 100644 index 0000000..ace568f --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/PermissionBucketHandlerTests.cs @@ -0,0 +1,113 @@ +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Buckets; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; +using Action = Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model.CcdUpdatePermissionByBucketRequest.ActionEnum; +using Permission = Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model.CcdUpdatePermissionByBucketRequest.PermissionEnum; +using Role = Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model.CcdUpdatePermissionByBucketRequest.RoleEnum; + +namespace CloudContentDeliveryTest.Handlers.Buckets; + +[TestFixture] +public class PermissionBucketHandlerTests +{ + readonly Mock m_MockUnityEnvironment = new(); + readonly Mock m_MockBucketClient = new(); + readonly Mock m_MockLogger = new(); + + [SetUp] + public void Setup() + { + m_MockUnityEnvironment.Reset(); + m_MockBucketClient.Reset(); + m_MockLogger.Reset(); + + var result = new CcdGetAllByBucket200ResponseInner(); + + m_MockBucketClient.Setup( + c => + c.UpdatePermissionBucketAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + CancellationToken.None)) + .ReturnsAsync(result); + + } + + [Test] + public async Task PermissionUpdateAsync_CallsLoadingIndicatorStartLoading() + { + var input = new CloudContentDeliveryInputBuckets(); + var mockLoadingIndicator = new Mock(); + var cancellationToken = CancellationToken.None; + + mockLoadingIndicator.Setup( + li => li.StartLoadingAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.CompletedTask); + + await PermissionBucketHandler.PermissionUpdateAsync( + input, + m_MockUnityEnvironment.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + mockLoadingIndicator.Object, + cancellationToken); + mockLoadingIndicator.Verify( + ex => ex.StartLoadingAsync(It.IsAny(), It.IsAny>()), + Times.Once); + } + + [Test] + [TestCase(Action.Write, Permission.Allow, Role.User)] + [TestCase(Action.Write, Permission.Allow, Role.Client)] + [TestCase(Action.Write, Permission.Deny, Role.User)] + [TestCase(Action.Write, Permission.Deny, Role.Client)] + [TestCase(Action.ListEntries, Permission.Allow, Role.User)] + [TestCase(Action.ListEntries, Permission.Allow, Role.Client)] + public async Task PermissionHandler_ValidInputLogsResult(Action action, Permission permission, Role role) + { + var cloudContentDeliveryInput = new CloudContentDeliveryInputBuckets + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketName = CloudContentDeliveryTestsConstants.BucketName, + Action = action, + Permission = permission, + Role = role + }; + var cancellationToken = CancellationToken.None; + + m_MockUnityEnvironment + .Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + await PermissionBucketHandler.PermissionUpdateAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + cancellationToken); + + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(cancellationToken), Times.Once); + m_MockBucketClient.Verify( + api => + api.UpdatePermissionBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + action, + permission, + role, + cancellationToken), + Times.Once); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/PromoteBucketHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/PromoteBucketHandlerTests.cs new file mode 100644 index 0000000..e615add --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/PromoteBucketHandlerTests.cs @@ -0,0 +1,142 @@ +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Buckets; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace CloudContentDeliveryTest.Handlers.Buckets; + +[TestFixture] +public class PromoteBucketHandlerTests +{ + readonly Mock m_MockUnityEnvironment = new(); + readonly Mock m_MockBucketClient = new(); + readonly Mock m_MockReleaseClient = new(); + readonly Mock m_MockLogger = new(); + + [SetUp] + public void Setup() + { + m_MockUnityEnvironment.Reset(); + m_MockBucketClient.Reset(); + m_MockLogger.Reset(); + + + m_MockUnityEnvironment + .Setup( + ue => ue.FetchIdentifierFromSpecificEnvironmentNameAsync( + CloudContentDeliveryTestsConstants.ToEnvironment, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + } + + [Test] + public async Task PromoteAsync_CallsLoadingIndicatorStartLoading() + { + var input = new CloudContentDeliveryInputBuckets(); + var mockLoadingIndicator = new Mock(); + var cancellationToken = CancellationToken.None; + + mockLoadingIndicator.Setup( + li => li.StartLoadingAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.CompletedTask); + await PromoteBucketHandler.PromoteAsync( + input, + m_MockUnityEnvironment.Object, + m_MockReleaseClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + mockLoadingIndicator.Object, + cancellationToken); + mockLoadingIndicator.Verify( + ex => ex.StartLoadingAsync(It.IsAny(), It.IsAny>()), + Times.Once); + } + + [Test] + public async Task PromoteHandler_ValidInputLogsResult() + { + var cloudContentDeliveryInput = new CloudContentDeliveryInputBuckets + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketNameOpt = CloudContentDeliveryTestsConstants.BucketName, + ReleaseNum = CloudContentDeliveryTestsConstants.ReleaseNumber, + Notes = CloudContentDeliveryTestsConstants.Notes, + TargetBucketName = CloudContentDeliveryTestsConstants.TargetBucketName, + TargetEnvironment = CloudContentDeliveryTestsConstants.ToEnvironment + }; + var cancellationToken = CancellationToken.None; + + m_MockBucketClient.Setup( + c => + c.GetBucketIdByName( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.BucketId); + + m_MockBucketClient.Setup( + c => + c.GetBucketIdByName( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.TargetBucketName, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.ToBucket); + + m_MockReleaseClient.Setup( + c => + c.GetReleaseIdByNumber( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.ReleaseNumber, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.PromotedFromRelease); + + m_MockBucketClient.Setup( + c => + c.PromoteBucketEnvAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.PromotedFromRelease, + CloudContentDeliveryTestsConstants.Notes, + CloudContentDeliveryTestsConstants.ToBucket, + CloudContentDeliveryTestsConstants.EnvironmentId, + CancellationToken.None)) + .ReturnsAsync(new CcdPromoteBucketAsync200Response()); + + m_MockUnityEnvironment.Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + await PromoteBucketHandler.PromoteAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockReleaseClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + cancellationToken); + + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(cancellationToken), Times.Once); + m_MockBucketClient.Verify( + api => + api.PromoteBucketEnvAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.PromotedFromRelease, + CloudContentDeliveryTestsConstants.Notes, + CloudContentDeliveryTestsConstants.ToBucket, + CloudContentDeliveryTestsConstants.EnvironmentId, + cancellationToken), + Times.Once); + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/PromotionBucketHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/PromotionBucketHandlerTests.cs new file mode 100644 index 0000000..a6093ca --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Buckets/PromotionBucketHandlerTests.cs @@ -0,0 +1,105 @@ +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Buckets; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace CloudContentDeliveryTest.Handlers.Buckets; + +[TestFixture] +public class PromotionBucketHandlerTests +{ + readonly Mock m_MockUnityEnvironment = new(); + readonly Mock m_MockBucketClient = new(); + readonly Mock m_MockLogger = new(); + + [SetUp] + public void Setup() + { + m_MockUnityEnvironment.Reset(); + m_MockBucketClient.Reset(); + m_MockLogger.Reset(); + + m_MockBucketClient.Setup( + c => + c.GetBucketIdByName( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.BucketId); + } + + [Test] + public async Task PromotionAsync_CallsLoadingIndicatorStartLoading() + { + var input = new CloudContentDeliveryInputBuckets(); + var mockLoadingIndicator = new Mock(); + var cancellationToken = CancellationToken.None; + + mockLoadingIndicator.Setup( + li => li.StartLoadingAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.CompletedTask); + + await PromotionBucketHandler.PromotionStatusAsync( + input, + m_MockUnityEnvironment.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + mockLoadingIndicator.Object, + cancellationToken); + + mockLoadingIndicator.Verify( + ex => ex.StartLoadingAsync(It.IsAny(), It.IsAny>()), + Times.Once); + } + + [Test] + public async Task PromotionHandler_ValidInputLogsResult() + { + var cloudContentDeliveryInput = new CloudContentDeliveryInputBuckets + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketNameOpt = CloudContentDeliveryTestsConstants.BucketName, + ReleaseId = CloudContentDeliveryTestsConstants.ReleaseId, + PromotionId = CloudContentDeliveryTestsConstants.PromotionId + }; + var cancellationToken = CancellationToken.None; + + m_MockBucketClient.Setup( + c => + c.GetPromotionAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.PromotionId, + CancellationToken.None)) + .ReturnsAsync(new CcdGetPromotions200ResponseInner()); + + m_MockUnityEnvironment.Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + await PromotionBucketHandler.PromotionStatusAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + cancellationToken); + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(cancellationToken), Times.Once); + m_MockBucketClient.Verify( + api => + api.GetPromotionAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.PromotionId, + cancellationToken), + Times.Once); + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/CopyEntryHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/CopyEntryHandlerTests.cs new file mode 100644 index 0000000..0ddea23 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/CopyEntryHandlerTests.cs @@ -0,0 +1,116 @@ +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Entries; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace CloudContentDeliveryTest.Handlers.Entries; + +[TestFixture] +public class CopyEntryHandlerTests +{ + readonly Mock m_MockUnityEnvironment = new(); + readonly Mock m_MockEntryClient = new(); + readonly Mock m_MockBucketClient = new(); + readonly Mock m_MockLogger = new(); + + [SetUp] + public void Setup() + { + m_MockUnityEnvironment.Reset(); + m_MockEntryClient.Reset(); + m_MockLogger.Reset(); + + m_MockBucketClient.Setup( + c => + c.GetBucketIdByName( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.BucketId); + } + + [Test] + public async Task CopyAsync_CallsLoadingIndicatorStartLoading() + { + var input = new CloudContentDeliveryInput(); + var mockLoadingIndicator = new Mock(); + var cancellationToken = CancellationToken.None; + + mockLoadingIndicator.Setup( + li => li.StartLoadingAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.CompletedTask); + + await CopyEntryHandler.CopyAsync( + input, + m_MockUnityEnvironment.Object, + m_MockEntryClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + mockLoadingIndicator.Object, + cancellationToken); + mockLoadingIndicator.Verify( + ex => ex.StartLoadingAsync(It.IsAny(), It.IsAny>()), + Times.Once); + } + + [Test] + public async Task CopyHandler_ValidInputLogsResult() + { + var cloudContentDeliveryInput = new CloudContentDeliveryInput + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketNameOpt = CloudContentDeliveryTestsConstants.BucketName, + LocalPath = CloudContentDeliveryTestsConstants.Path, + RemotePath = CloudContentDeliveryTestsConstants.Path, + Labels = CloudContentDeliveryTestsConstants.Labels, + Metadata = CloudContentDeliveryTestsConstants.Metadata + + }; + var cancellationToken = CancellationToken.None; + + m_MockUnityEnvironment.Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + m_MockEntryClient.Setup( + c => + c.CopyEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata, + CancellationToken.None)) + .ReturnsAsync(new CcdCreateOrUpdateEntryBatch200ResponseInner()); + + await CopyEntryHandler.CopyAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockEntryClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + cancellationToken); + + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(cancellationToken), Times.Once); + m_MockEntryClient.Verify( + api => + api.CopyEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata, + cancellationToken), + Times.Once); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/DeleteEntryHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/DeleteEntryHandlerTests.cs new file mode 100644 index 0000000..d2a44f0 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/DeleteEntryHandlerTests.cs @@ -0,0 +1,105 @@ +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Entries; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; + +namespace CloudContentDeliveryTest.Handlers.Entries; + +[TestFixture] +public class DeleteEntryHandlerTests +{ + readonly Mock m_MockUnityEnvironment = new(); + readonly Mock m_MockEntryClient = new(); + readonly Mock m_MockBucketClient = new(); + readonly Mock m_MockLogger = new(); + + [SetUp] + public void Setup() + { + m_MockUnityEnvironment.Reset(); + m_MockEntryClient.Reset(); + m_MockLogger.Reset(); + + m_MockBucketClient.Setup( + c => + c.GetBucketIdByName( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.BucketId); + } + + [Test] + public async Task DeleteAsync_CallsLoadingIndicatorStartLoading() + { + var input = new CloudContentDeliveryInput(); + var mockLoadingIndicator = new Mock(); + var cancellationToken = CancellationToken.None; + + mockLoadingIndicator.Setup( + li => li.StartLoadingAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.CompletedTask); + await DeleteEntryHandler.DeleteAsync( + input, + m_MockUnityEnvironment.Object, + m_MockEntryClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + mockLoadingIndicator.Object, + cancellationToken); + mockLoadingIndicator.Verify( + ex => ex.StartLoadingAsync(It.IsAny(), It.IsAny>()), + Times.Once); + } + + [Test] + public async Task DeleteHandler_ValidInputLogsResult() + { + var cloudContentDeliveryInput = new CloudContentDeliveryInput + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketNameOpt = CloudContentDeliveryTestsConstants.BucketName, + EntryPath = CloudContentDeliveryTestsConstants.Path + + }; + var cancellationToken = CancellationToken.None; + + m_MockEntryClient.Setup( + c => + c.DeleteEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CancellationToken.None)) + .ReturnsAsync("Entry Deleted."); + + m_MockUnityEnvironment.Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + await DeleteEntryHandler.DeleteAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockEntryClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + cancellationToken); + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(cancellationToken), Times.Once); + m_MockEntryClient.Verify( + api => + api.DeleteEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + cancellationToken), + Times.Once); + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/DownloadEntryHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/DownloadEntryHandlerTests.cs new file mode 100644 index 0000000..7c997db --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/DownloadEntryHandlerTests.cs @@ -0,0 +1,108 @@ +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Entries; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; + +namespace CloudContentDeliveryTest.Handlers.Entries; + +[TestFixture] +public class DownloadEntryHandlerTests +{ + readonly Mock m_MockUnityEnvironment = new(); + readonly Mock m_MockEntryClient = new(); + readonly Mock m_MockBucketClient = new(); + readonly Mock m_MockLogger = new(); + + [SetUp] + public void Setup() + { + m_MockUnityEnvironment.Reset(); + m_MockEntryClient.Reset(); + m_MockLogger.Reset(); + + m_MockBucketClient.Setup( + c => + c.GetBucketIdByName( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.BucketId); + } + + [Test] + public async Task DownloadAsync_CallsLoadingIndicatorStartLoading() + { + var input = new CloudContentDeliveryInput(); + var mockLoadingIndicator = new Mock(); + var cancellationToken = CancellationToken.None; + + mockLoadingIndicator.Setup( + li => li.StartLoadingAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.CompletedTask); + await DownloadEntryHandler.DownloadAsync( + input, + m_MockUnityEnvironment.Object, + m_MockEntryClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + mockLoadingIndicator.Object, + cancellationToken); + mockLoadingIndicator.Verify( + ex => ex.StartLoadingAsync(It.IsAny(), It.IsAny>()), + Times.Once); + } + + [Test] + public async Task DownloadHandler_ValidInputLogsResult() + { + var cloudContentDeliveryInput = new CloudContentDeliveryInput + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketNameOpt = CloudContentDeliveryTestsConstants.BucketName, + EntryPath = CloudContentDeliveryTestsConstants.Path, + VersionId = CloudContentDeliveryTestsConstants.VersionId + + }; + var cancellationToken = CancellationToken.None; + + m_MockEntryClient.Setup( + c => + c.DownloadEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.VersionId, + CancellationToken.None)) + .ReturnsAsync(new MemoryStream()); + + m_MockUnityEnvironment.Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + await DownloadEntryHandler.DownloadAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockEntryClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + cancellationToken); + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(cancellationToken), Times.Once); + m_MockEntryClient.Verify( + api => + api.DownloadEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.VersionId, + cancellationToken), + Times.Once); + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/GetEntryHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/GetEntryHandlerTests.cs new file mode 100644 index 0000000..1c8574f --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/GetEntryHandlerTests.cs @@ -0,0 +1,109 @@ +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Entries; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace CloudContentDeliveryTest.Handlers.Entries; + +[TestFixture] +public class GetEntryHandlerTests +{ + readonly Mock m_MockUnityEnvironment = new(); + readonly Mock m_MockEntryClient = new(); + readonly Mock m_MockBucketClient = new(); + readonly Mock m_MockLogger = new(); + + [SetUp] + public void Setup() + { + m_MockUnityEnvironment.Reset(); + m_MockEntryClient.Reset(); + m_MockLogger.Reset(); + + m_MockBucketClient.Setup( + c => + c.GetBucketIdByName( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.BucketId); + } + + [Test] + public async Task GetEntryAsync_CallsLoadingIndicatorStartLoading() + { + var input = new CloudContentDeliveryInput(); + var mockLoadingIndicator = new Mock(); + var cancellationToken = CancellationToken.None; + + mockLoadingIndicator.Setup( + li => li.StartLoadingAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.CompletedTask); + await GetEntryHandler.GetAsync( + input, + m_MockUnityEnvironment.Object, + m_MockEntryClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + mockLoadingIndicator.Object, + cancellationToken); + mockLoadingIndicator.Verify( + ex => ex.StartLoadingAsync(It.IsAny(), It.IsAny>()), + Times.Once); + } + + [Test] + public async Task GetHandler_ValidInputLogsResult() + { + var cloudContentDeliveryInput = new CloudContentDeliveryInput + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketNameOpt = CloudContentDeliveryTestsConstants.BucketName, + EntryPath = CloudContentDeliveryTestsConstants.Path, + VersionId = CloudContentDeliveryTestsConstants.VersionId + + }; + var cancellationToken = CancellationToken.None; + + m_MockEntryClient.Setup( + c => + c.GetEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.VersionId, + CancellationToken.None)) + .ReturnsAsync(new CcdCreateOrUpdateEntryBatch200ResponseInner()); + + m_MockUnityEnvironment.Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + await GetEntryHandler.GetAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockEntryClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + cancellationToken); + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(cancellationToken), Times.Once); + m_MockEntryClient.Verify( + api => + api.GetEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.VersionId, + cancellationToken), + Times.Once); + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/ListEntryHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/ListEntryHandlerTests.cs new file mode 100644 index 0000000..f980f3c --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/ListEntryHandlerTests.cs @@ -0,0 +1,138 @@ +using System.Net; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Entries; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace CloudContentDeliveryTest.Handlers.Entries; + +[TestFixture] +public class ListEntryHandlerTests +{ + readonly Mock m_MockUnityEnvironment = new(); + readonly Mock m_MockEntryClient = new(); + readonly Mock m_MockBucketClient = new(); + readonly Mock m_MockLogger = new(); + + [SetUp] + public void Setup() + { + m_MockUnityEnvironment.Reset(); + m_MockEntryClient.Reset(); + m_MockLogger.Reset(); + + m_MockBucketClient.Setup( + c => + c.GetBucketIdByName( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.BucketId); + } + + [Test] + public async Task ListAsync_CallsLoadingIndicatorStartLoading() + { + var input = new CloudContentDeliveryInput(); + var mockLoadingIndicator = new Mock(); + var cancellationToken = CancellationToken.None; + + mockLoadingIndicator.Setup( + li => li.StartLoadingAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.CompletedTask); + + await ListEntryHandler.ListAsync( + input, + m_MockUnityEnvironment.Object, + m_MockEntryClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + mockLoadingIndicator.Object, + cancellationToken); + mockLoadingIndicator.Verify( + ex => ex.StartLoadingAsync(It.IsAny(), It.IsAny>()), + Times.Once); + } + + [Test] + public async Task ListHandler_ValidInputLogsResult() + { + var cloudContentDeliveryInput = new CloudContentDeliveryInput + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketNameOpt = CloudContentDeliveryTestsConstants.BucketName, + Page = CloudContentDeliveryTestsConstants.Page, + PerPage = CloudContentDeliveryTestsConstants.PerPage, + StartingAfter = CloudContentDeliveryTestsConstants.StartingAfter, + Path = CloudContentDeliveryTestsConstants.Path, + Label = CloudContentDeliveryTestsConstants.Label, + ContentType = CloudContentDeliveryTestsConstants.ContentType, + Complete = CloudContentDeliveryTestsConstants.Complete, + SortByEntry = CloudContentDeliveryTestsConstants.SortBy, + SortOrder = CloudContentDeliveryTestsConstants.SortOrder + + }; + var cancellationToken = CancellationToken.None; + + m_MockUnityEnvironment.Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + m_MockEntryClient.Setup( + c => + c.ListEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Page, + CloudContentDeliveryTestsConstants.StartingAfter, + CloudContentDeliveryTestsConstants.PerPage, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.Label, + CloudContentDeliveryTestsConstants.ContentType, + CloudContentDeliveryTestsConstants.Complete, + CloudContentDeliveryTestsConstants.SortBy, + CloudContentDeliveryTestsConstants.SortOrder, + CancellationToken.None)) + .ReturnsAsync( + new ApiResponse>( + HttpStatusCode.OK, + new Multimap(), + new List() + )); + + await ListEntryHandler.ListAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockEntryClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + cancellationToken); + + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(cancellationToken), Times.Once); + m_MockEntryClient.Verify( + api => + api.ListEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Page, + CloudContentDeliveryTestsConstants.StartingAfter, + CloudContentDeliveryTestsConstants.PerPage, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.Label, + CloudContentDeliveryTestsConstants.ContentType, + CloudContentDeliveryTestsConstants.Complete, + CloudContentDeliveryTestsConstants.SortBy, + CloudContentDeliveryTestsConstants.SortOrder, + cancellationToken), + Times.Once); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/SyncEntryHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/SyncEntryHandlerTests.cs new file mode 100644 index 0000000..2b8a054 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/SyncEntryHandlerTests.cs @@ -0,0 +1,199 @@ +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Entries; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.CloudContentDelivery.Utils; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace CloudContentDeliveryTest.Handlers.Entries; + +[TestFixture] +public class SyncEntryHandlerTests +{ + readonly Mock m_MockUnityEnvironment = new(); + readonly Mock m_MockEntryClient = new(); + readonly Mock m_MockLogger = new(); + readonly Mock m_MockClientWrapper = new(); + readonly Mock m_MockSynchronizationService = new(); + + [SetUp] + public void Setup() + { + + + m_MockUnityEnvironment.Reset(); + m_MockEntryClient.Reset(); + m_MockLogger.Reset(); + + m_MockClientWrapper.Setup( + c => + c.BucketClient!.GetBucketIdByName( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.BucketId); + } + + [Test] + public async Task SyncEntryAsync_CallsLoadingIndicatorStartLoading() + { + var input = new CloudContentDeliveryInput(); + var mockLoadingIndicator = new Mock(); + var cancellationToken = CancellationToken.None; + + mockLoadingIndicator.Setup( + li => li.StartLoadingAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.CompletedTask); + await SyncEntryHandler.SyncEntriesAsync( + input, + m_MockUnityEnvironment.Object, + m_MockClientWrapper.Object, + m_MockSynchronizationService.Object, + m_MockLogger.Object, + mockLoadingIndicator.Object, + cancellationToken); + mockLoadingIndicator.Verify( + ex => ex.StartLoadingAsync(It.IsAny(), It.IsAny>()), + Times.Once); + } + + [Test] + public async Task SyncHandler_ValidInputLogsResult() + { + var cloudContentDeliveryInput = new CloudContentDeliveryInput + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketNameOpt = CloudContentDeliveryTestsConstants.BucketName, + EntryPath = CloudContentDeliveryTestsConstants.Path, + VersionId = CloudContentDeliveryTestsConstants.VersionId, + LocalFolder = CloudContentDeliveryTestsConstants.LocalFolder, + ExclusionPattern = CloudContentDeliveryTestsConstants.ExclusionPattern, + Delete = CloudContentDeliveryTestsConstants.Delete, + Retry = CloudContentDeliveryTestsConstants.Retry, + DryRun = CloudContentDeliveryTestsConstants.DryRun, + UpdateBadge = CloudContentDeliveryTestsConstants.UpdateBadge, + CreateRelease = CloudContentDeliveryTestsConstants.CreateRelease, + SyncMetadata = CloudContentDeliveryTestsConstants.Metadata, + ReleaseNotes = CloudContentDeliveryTestsConstants.Notes, + Labels = CloudContentDeliveryTestsConstants.Labels + }; + + var expectedSyncResult = new SyncResult(); + m_MockSynchronizationService.Setup( + s => s.CalculateSynchronization( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.LocalFolder, + CloudContentDeliveryTestsConstants.ExclusionPattern, + CloudContentDeliveryTestsConstants.Delete, + CloudContentDeliveryTestsConstants.Labels, + CcdUtils.ParseMetadata(CloudContentDeliveryTestsConstants.Metadata), + CancellationToken.None)) + .ReturnsAsync(expectedSyncResult); + + m_MockSynchronizationService.Setup( + s => s.ProcessSynchronization( + m_MockLogger.Object, + true, + expectedSyncResult, + CloudContentDeliveryTestsConstants.LocalFolder, + CloudContentDeliveryTestsConstants.Retry, + 50, + 100, + CancellationToken.None)) + .ReturnsAsync(new List()); + + m_MockClientWrapper.Setup( + r => r.ReleaseClient!.CreateReleaseAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + It.IsAny(), + CancellationToken.None)) + .ReturnsAsync(new CcdGetBucket200ResponseLastRelease()); + + m_MockClientWrapper.Setup( + b => b.BadgeClient!.CreateBadgeAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + "", + 0, + CancellationToken.None)) + .ReturnsAsync( + new CcdGetBucket200ResponseLastReleaseBadgesInner + { + Name = CloudContentDeliveryTestsConstants.BadgeName + }); + + m_MockUnityEnvironment.Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + await SyncEntryHandler.SyncEntriesAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockClientWrapper.Object, + m_MockSynchronizationService.Object, + m_MockLogger.Object, + CancellationToken.None); + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(CancellationToken.None), Times.Once); + + } + + [Test] + public async Task SyncHandler_ValidInputLogsResultWithDryRunNoCreateRelease() + { + var cloudContentDeliveryInput = new CloudContentDeliveryInput + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketNameOpt = CloudContentDeliveryTestsConstants.BucketName, + EntryPath = CloudContentDeliveryTestsConstants.Path, + VersionId = CloudContentDeliveryTestsConstants.VersionId, + LocalFolder = CloudContentDeliveryTestsConstants.LocalFolder, + ExclusionPattern = CloudContentDeliveryTestsConstants.ExclusionPattern, + Delete = CloudContentDeliveryTestsConstants.Delete, + Retry = CloudContentDeliveryTestsConstants.Retry, + DryRun = true, + UpdateBadge = CloudContentDeliveryTestsConstants.UpdateBadge, + CreateRelease = false, + SyncMetadata = CloudContentDeliveryTestsConstants.Metadata, + ReleaseNotes = CloudContentDeliveryTestsConstants.Notes, + Labels = CloudContentDeliveryTestsConstants.Labels + }; + + var expectedSyncResult = new SyncResult(); + m_MockSynchronizationService.Setup( + s => s.CalculateSynchronization( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.LocalFolder, + CloudContentDeliveryTestsConstants.ExclusionPattern, + CloudContentDeliveryTestsConstants.Delete, + CloudContentDeliveryTestsConstants.Labels, + CcdUtils.ParseMetadata(CloudContentDeliveryTestsConstants.Metadata), + CancellationToken.None)) + .ReturnsAsync(expectedSyncResult); + + m_MockUnityEnvironment.Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + await SyncEntryHandler.SyncEntriesAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockClientWrapper.Object, + m_MockSynchronizationService.Object, + m_MockLogger.Object, + CancellationToken.None); + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(CancellationToken.None), Times.Once); + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/UpdateEntryHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/UpdateEntryHandlerTests.cs new file mode 100644 index 0000000..29d4f82 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Handlers/Entries/UpdateEntryHandlerTests.cs @@ -0,0 +1,115 @@ +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Entries; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace CloudContentDeliveryTest.Handlers.Entries; + +[TestFixture] +public class UpdateEntryHandlerTests +{ + readonly Mock m_MockUnityEnvironment = new(); + readonly Mock m_MockEntryClient = new(); + readonly Mock m_MockBucketClient = new(); + readonly Mock m_MockLogger = new(); + + [SetUp] + public void Setup() + { + m_MockUnityEnvironment.Reset(); + m_MockEntryClient.Reset(); + m_MockLogger.Reset(); + + m_MockBucketClient.Setup( + c => + c.GetBucketIdByName( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.BucketId); + } + + [Test] + public async Task UpdateEntryAsync_CallsLoadingIndicatorStartLoading() + { + var input = new CloudContentDeliveryInput(); + var mockLoadingIndicator = new Mock(); + var cancellationToken = CancellationToken.None; + + mockLoadingIndicator.Setup( + li => li.StartLoadingAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.CompletedTask); + await UpdateEntryHandler.UpdateAsync( + input, + m_MockUnityEnvironment.Object, + m_MockEntryClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + mockLoadingIndicator.Object, + cancellationToken); + mockLoadingIndicator.Verify( + ex => ex.StartLoadingAsync(It.IsAny(), It.IsAny>()), + Times.Once); + } + + [Test] + public async Task UpdateHandler_ValidInputLogsResult() + { + var cloudContentDeliveryInput = new CloudContentDeliveryInput + { + CloudProjectId = CloudContentDeliveryTestsConstants.ProjectId, + BucketNameOpt = CloudContentDeliveryTestsConstants.BucketName, + EntryPath = CloudContentDeliveryTestsConstants.Path, + VersionId = CloudContentDeliveryTestsConstants.VersionId, + Labels = CloudContentDeliveryTestsConstants.Labels, + Metadata = CloudContentDeliveryTestsConstants.Metadata + + }; + var cancellationToken = CancellationToken.None; + + m_MockEntryClient.Setup( + c => + c.UpdateEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.VersionId, + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata, + CancellationToken.None)) + .ReturnsAsync(new CcdCreateOrUpdateEntryBatch200ResponseInner()); + + m_MockUnityEnvironment.Setup(x => x.FetchIdentifierAsync(CancellationToken.None)) + .ReturnsAsync(CloudContentDeliveryTestsConstants.EnvironmentId); + + await UpdateEntryHandler.UpdateAsync( + cloudContentDeliveryInput, + m_MockUnityEnvironment.Object, + m_MockEntryClient.Object, + m_MockBucketClient.Object, + m_MockLogger.Object, + cancellationToken); + m_MockUnityEnvironment.Verify(x => x.FetchIdentifierAsync(cancellationToken), Times.Once); + m_MockEntryClient.Verify( + api => + api.UpdateEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.VersionId, + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata, + cancellationToken), + Times.Once); + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Models/ModelsTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Models/ModelsTests.cs new file mode 100644 index 0000000..a347770 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Models/ModelsTests.cs @@ -0,0 +1,53 @@ +using NUnit.Framework; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.Common.Exceptions; + +// ReSharper disable ObjectCreationAsStatement + +namespace CloudContentDeliveryTest.Models; + +[TestFixture] +public class ModelsTests +{ + [Test] + public void BadgeResult_Constructor_HandleNullInput() + { + Assert.Throws(() => new BadgeResult(null)); + } + + [Test] + public void BucketResult_Constructor_HandleNullInput() + { + Assert.Throws(() => new BucketResult(null)); + } + + [Test] + public void EntryResult_Constructor_HandleNullInput() + { + Assert.Throws(() => new EntryResult(null)); + } + + [Test] + public void PromoteResult_Constructor_HandleNullInput() + { + Assert.Throws(() => new PromoteResult(null)); + } + + [Test] + public void PromotionResult_Constructor_HandleNullInput() + { + Assert.Throws(() => new PromotionResult(null)); + } + + [Test] + public void ReleaseResult_Constructor_HandleNullInput() + { + Assert.Throws(() => new ReleaseResult(null)); + } + + [Test] + public void PermissionResult_Constructor_HandlesNullInput() + { + Assert.Throws(() => new PermissionResult(null)); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/BadgeClientTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/BadgeClientTests.cs new file mode 100644 index 0000000..2df03ec --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/BadgeClientTests.cs @@ -0,0 +1,214 @@ +using System.Net; +using Moq; +using NUnit.Framework; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Cli.Common.Models; +using Unity.Services.Cli.ServiceAccountAuthentication; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Api; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; +using Configuration = Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client.Configuration; + +namespace CloudContentDeliveryTest.Service; + +[TestFixture] +public class BadgeClientTests +{ + readonly Mock m_AuthServiceMock = new(); + readonly Mock m_BadgesApiMock = new(); + readonly Mock m_ContentDeliveryValidator = new(); + readonly Mock m_HttpClient = new(); + BadgeClient m_BadgeClient = null!; + const string k_TestAccessToken = "test-token"; + + [SetUp] + public void SetUp() + { + m_AuthServiceMock.Reset(); + m_BadgesApiMock.Reset(); + m_HttpClient.Reset(); + m_ContentDeliveryValidator.Reset(); + m_BadgesApiMock.Setup(api => api.Configuration).Returns(new Configuration()); + + m_AuthServiceMock.Setup(a => a.GetAccessTokenAsync(CancellationToken.None)) + .Returns(Task.FromResult(k_TestAccessToken)); + + m_ContentDeliveryValidator.Setup( + v => v.ValidateProjectIdAndEnvironmentId(It.IsAny(), It.IsAny())); + + m_BadgeClient = new BadgeClient( + m_AuthServiceMock.Object, + m_BadgesApiMock.Object, + m_ContentDeliveryValidator.Object); + } + + [Test] + public async Task AuthorizeServiceAsync() + { + await m_BadgeClient.AuthorizeServiceAsync(CancellationToken.None); + m_AuthServiceMock.Verify(a => a.GetAccessTokenAsync(CancellationToken.None), Times.Once); + Assert.Multiple( + () => + { + Assert.That( + m_BadgesApiMock.Object.Configuration.DefaultHeaders["Authorization"], + Is.EqualTo($"Basic {k_TestAccessToken}")); + }); + } + + [Test] + public void DeleteBadgeAsync_AuthenticationFails_ThrowsException() + { + m_AuthServiceMock.Reset(); + m_AuthServiceMock.Setup(a => a.GetAccessTokenAsync(CancellationToken.None)) + .ThrowsAsync(new Exception("Authentication failed")); + + Assert.ThrowsAsync( + async () => + { + await m_BadgeClient.DeleteBadgeAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.BadgeName, + CancellationToken.None); + }); + } + + [Test] + public void DeleteBadgeAsync_InvalidProjectIdException() + { + m_ContentDeliveryValidator + .Setup( + v => v.ValidateProjectIdAndEnvironmentId( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId)) + .Throws(new ConfigValidationException(Keys.ConfigKeys.ProjectId, "", It.IsAny())); + + var exception = Assert.ThrowsAsync( + async () => + { + await m_BadgeClient.DeleteBadgeAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.BadgeName, + CancellationToken.None); + }); + + Assert.That(exception?.Message, Is.EqualTo("Your project-id is not valid. ")); + m_ContentDeliveryValidator.Verify( + v => v.ValidateProjectIdAndEnvironmentId( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId), + Times.Once); + } + + [Test] + public void DeleteBadgeAsync_InvalidEnvironmentIdException() + { + m_ContentDeliveryValidator.Setup( + v => v.ValidateProjectIdAndEnvironmentId( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId)) + .Throws(new ConfigValidationException(Keys.ConfigKeys.EnvironmentId, "", It.IsAny())); + var exception = Assert.ThrowsAsync( + async () => + { + await m_BadgeClient.DeleteBadgeAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.BadgeName, + CancellationToken.None); + }); + + Assert.That(exception?.Message, Is.EqualTo("Your environment-id is not valid. ")); + + } + + [Test] + public async Task ListBadgeAsync_ValidParameters_ReturnsBadges() + { + + var expectedBadgeList = new ApiResponse>( + HttpStatusCode.OK, + new Multimap(), + new List + { + new(), + new() + } + ); + + m_BadgesApiMock.Setup( + api => api.ListBadgesEnvWithHttpInfoAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.Page, + CloudContentDeliveryTestsConstants.PerPage, + CloudContentDeliveryTestsConstants.BadgeName, + CloudContentDeliveryTestsConstants.ReleaseNumber.ToString(), + CloudContentDeliveryTestsConstants.SortBy, + CloudContentDeliveryTestsConstants.SortOrder, + 0, + CancellationToken.None)) + .ReturnsAsync(expectedBadgeList); + + var result = await m_BadgeClient.ListBadgeAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Page, + CloudContentDeliveryTestsConstants.PerPage, + CloudContentDeliveryTestsConstants.BadgeName, + CloudContentDeliveryTestsConstants.ReleaseNumber.ToString(), + CloudContentDeliveryTestsConstants.SortBy, + CloudContentDeliveryTestsConstants.SortOrder, + CancellationToken.None); + + Assert.That(result.Data, Is.Not.Null); + Assert.That(result.Data, Has.Count.EqualTo(expectedBadgeList.Data.Count)); + } + + [Test] + public async Task AddBadgeAsync_ValidParameters_ReturnsBadge() + { + var expectedBadgeResponse = new CcdGetBucket200ResponseLastReleaseBadgesInner(); + + m_BadgesApiMock.Setup( + api => api.UpdateBadgeEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.ProjectId, + It.IsAny(), + 0, + CancellationToken.None)) + .ReturnsAsync(expectedBadgeResponse); + + var result = await m_BadgeClient.CreateBadgeAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.BadgeName, + CloudContentDeliveryTestsConstants.ReleaseNumber, + CancellationToken.None); + + Assert.That(result, Is.EqualTo(expectedBadgeResponse)); + } + + [Test] + public async Task DeleteBadgeAsync_ValidParameters_ReturnsDeletedMessage() + { + var result = await m_BadgeClient.DeleteBadgeAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.BadgeName, + CancellationToken.None); + Assert.That(result, Is.EqualTo("Badge deleted.")); + } + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/BucketClientTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/BucketClientTests.cs new file mode 100644 index 0000000..d8c1634 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/BucketClientTests.cs @@ -0,0 +1,344 @@ +using System.Net; +using Moq; +using NUnit.Framework; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Cli.Common.Models; +using Unity.Services.Cli.ServiceAccountAuthentication; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Api; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; +using Configuration = Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client.Configuration; + +namespace CloudContentDeliveryTest.Service; + +[TestFixture] +public class BucketClientTests +{ + + readonly Mock m_AuthServiceMock = new(); + readonly Mock m_BucketsApiMock = new(); + readonly Mock m_PermissionsApiMock = new(); + + readonly Mock m_ContentDeliveryValidator = new(); + BucketClient m_BucketClient = null!; + + const string k_TestAccessToken = "test-token"; + + [SetUp] + public void SetUp() + { + m_AuthServiceMock.Reset(); + m_BucketsApiMock.Reset(); + m_PermissionsApiMock.Reset(); + m_ContentDeliveryValidator.Reset(); + m_BucketsApiMock.Setup(api => api.Configuration).Returns(new Configuration()); + m_PermissionsApiMock.Setup(api => api.Configuration).Returns(new Configuration()); + + m_BucketsApiMock.Setup( + x => x.ListBucketsByProjectEnvWithHttpInfoAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + CloudContentDeliveryTestsConstants.BucketName, + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ReturnsAsync( + new ApiResponse>( + HttpStatusCode.OK, + new Multimap(), + new List + {new() + { + Name = "NewBucket", + Id = new Guid(CloudContentDeliveryTestsConstants.BucketId) + }} + )); + + m_BucketsApiMock.Setup( + x => x.ListBucketsByProjectEnvAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + "missing-bucket", + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ReturnsAsync( + new List( + )); + + m_AuthServiceMock.Setup(a => a.GetAccessTokenAsync(CancellationToken.None)) + .Returns(Task.FromResult(k_TestAccessToken)); + + m_BucketClient = new BucketClient( + m_AuthServiceMock.Object, + m_BucketsApiMock.Object, + m_PermissionsApiMock.Object, + m_ContentDeliveryValidator.Object); + } + + [Test] + public async Task AuthorizeServiceAsync() + { + await m_BucketClient.AuthorizeServiceAsync(CancellationToken.None); + m_AuthServiceMock.Verify(a => a.GetAccessTokenAsync(CancellationToken.None), Times.Once); + Assert.Multiple( + () => + { + Assert.That( + m_BucketsApiMock.Object.Configuration.DefaultHeaders["Authorization"], + Is.EqualTo($"Basic {k_TestAccessToken}")); + Assert.That( + m_PermissionsApiMock.Object.Configuration.DefaultHeaders["Authorization"], + Is.EqualTo($"Basic {k_TestAccessToken}")); + }); + } + + [Test] + public void GetBucketAsync_AuthenticationFails_ThrowsException() + { + m_AuthServiceMock.Reset(); + m_AuthServiceMock.Setup(a => a.GetAccessTokenAsync(CancellationToken.None)) + .ThrowsAsync(new Exception("Authentication failed")); + + Assert.ThrowsAsync( + async () => + { + await m_BucketClient.GetBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId); + }); + } + + [Test] + public void GetBucketAsync_InvalidProjectIdException() + { + m_ContentDeliveryValidator + .Setup( + v => v.ValidateProjectIdAndEnvironmentId( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId)) + .Throws(new ConfigValidationException(Keys.ConfigKeys.ProjectId, "", It.IsAny())); + + var exception = Assert.ThrowsAsync( + async () => + { + await m_BucketClient.GetBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId); + }); + + Assert.That(exception?.Message, Is.EqualTo("Your project-id is not valid. ")); + m_ContentDeliveryValidator.Verify( + v => v.ValidateProjectIdAndEnvironmentId( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId), + Times.Once); + } + + [Test] + public void GetBucketAsync_InvalidEnvironmentIdException() + { + m_ContentDeliveryValidator.Setup( + v => v.ValidateProjectIdAndEnvironmentId( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId)) + .Throws(new ConfigValidationException(Keys.ConfigKeys.EnvironmentId, "", It.IsAny())); + var exception = Assert.ThrowsAsync( + async () => + { + await m_BucketClient.GetBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId); + }); + + Assert.That(exception?.Message, Is.EqualTo("Your environment-id is not valid. ")); + + } + + [Test] + public async Task GetBucketAsync_BucketExists_ReturnsValidBucket() + { + var expectedBucketResponse = new CcdGetBucket200Response + { + Id = new Guid(CloudContentDeliveryTestsConstants.BucketId), + Projectguid = new Guid(CloudContentDeliveryTestsConstants.ProjectId) + }; + + m_BucketsApiMock.Setup( + api => api.GetBucketEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.ProjectId, + 0, + CancellationToken.None)) + .ReturnsAsync(expectedBucketResponse); + + var result = await m_BucketClient.GetBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName); + + Assert.That(result, Is.Not.Null); + Assert.Multiple( + () => + { + Assert.That(result!.Projectguid, Is.EqualTo(expectedBucketResponse.Projectguid)); + Assert.That(result.Id, Is.EqualTo(expectedBucketResponse.Id)); + }); + } + + [Test] + public void GetBucketAsync_BucketDoesNotExist_ThrowsException() + { + var bucketId = "missing-bucket"; + m_BucketsApiMock.Setup( + api => api.GetBucketEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + bucketId, + CloudContentDeliveryTestsConstants.ProjectId, + 0, + CancellationToken.None)) + .ThrowsAsync(new Exception("Bucket not found")); + + Assert.ThrowsAsync( + async () => + { + await m_BucketClient.GetBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + bucketId); + }); + } + + [Test] + public async Task DeleteBucketAsync_BucketExists_ReturnsDeletedMessage() + { + var result = await m_BucketClient.DeleteBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName); + Assert.That(result, Is.EqualTo("Bucket deleted.")); + } + + [Test] + public void DeleteBucketAsync_BucketDoesNotExist_ThrowsException() + { + var bucketId = "missing-bucket"; + + Assert.ThrowsAsync( + async () => + { + await m_BucketClient.DeleteBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + bucketId); + }); + } + + [Test] + public async Task ListBucketAsync_ValidParameters_ReturnsBuckets() + { + var expectedBucketList = new ApiResponse>( + HttpStatusCode.OK, + new Multimap(), + new List + { + new(), + new() + } + ); + + m_BucketsApiMock.Setup( + api => api.ListBucketsByProjectEnvWithHttpInfoAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.Page, + CloudContentDeliveryTestsConstants.PerPage, + CloudContentDeliveryTestsConstants.BucketName, + CloudContentDeliveryTestsConstants.BucketDescription, + CloudContentDeliveryTestsConstants.SortBy, + CloudContentDeliveryTestsConstants.SortOrder, + default, + CancellationToken.None)) + .ReturnsAsync(expectedBucketList); + + var result = await m_BucketClient.ListBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.Page, + CloudContentDeliveryTestsConstants.PerPage, + CloudContentDeliveryTestsConstants.BucketName, + CloudContentDeliveryTestsConstants.BucketDescription, + CloudContentDeliveryTestsConstants.SortBy, + CloudContentDeliveryTestsConstants.SortOrder, + CancellationToken.None); + + Assert.That(result.Data, Is.Not.Null); + Assert.That(result.Data, Has.Count.EqualTo(expectedBucketList.Data.Count)); + } + + [Test] + public async Task CreateBucketAsync_ValidParameters_ReturnsBucketResult() + { + var expectedBucketResult = new CcdGetBucket200Response(); + + m_BucketsApiMock.Setup( + api => api.CreateBucketByProjectEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.ProjectId, + It.IsAny(), + 0, + CancellationToken.None)) + .ReturnsAsync(new CcdGetBucket200Response()); + + var result = await m_BucketClient.CreateBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + CloudContentDeliveryTestsConstants.BucketDescription, + true); + + Assert.IsNotNull(result); + + Assert.That(result, Is.EqualTo(expectedBucketResult)); + } + + [Test] + public void CreateBucketAsync_InvalidParameters_ThrowsSpecificException() + { + m_BucketsApiMock.Setup( + api => api.CreateBucketByProjectEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.ProjectId, + It.IsAny(), + 0, + CancellationToken.None)) + .ThrowsAsync(new Exception("Invalid parameters")); + + Assert.ThrowsAsync( + async () => + { + await m_BucketClient.CreateBucketAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketName, + "", + true); + }); + } + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/ContentDeliveryValidatorTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/ContentDeliveryValidatorTests.cs new file mode 100644 index 0000000..21bc643 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/ContentDeliveryValidatorTests.cs @@ -0,0 +1,78 @@ +using Moq; +using NUnit.Framework; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Cli.Common.Models; +using Unity.Services.Cli.Common.Validator; + +namespace CloudContentDeliveryTest.Service + +{ + [TestFixture] + public class ContentDeliveryValidatorTests + { + + readonly Mock m_ConfigValidator = new(); + ContentDeliveryValidator m_ContentDeliveryValidator = null!; + + [SetUp] + public void Setup() + { + m_ContentDeliveryValidator = new ContentDeliveryValidator(m_ConfigValidator.Object); + } + + [Test] + public void ValidateProjectIdAndEnvironmentId_ValidIds_DoesNotThrowException() + { + Assert.DoesNotThrow( + () => m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId)); + } + + [Test] + public void ValidateProjectIdAndEnvironmentId_InvalidProjectId_ThrowsConfigValidationException() + { + m_ConfigValidator.Setup(v => v.ThrowExceptionIfConfigInvalid(Keys.ConfigKeys.ProjectId, "")) + .Throws(new ConfigValidationException(Keys.ConfigKeys.ProjectId, "", It.IsAny())); + Assert.Throws( + () => m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId( + "", + CloudContentDeliveryTestsConstants.EnvironmentId)); + } + + [Test] + public void ValidateBucketId_ValidBucketId_DoesNotThrowException() + { + Assert.DoesNotThrow( + () => m_ContentDeliveryValidator.ValidateBucketId(CloudContentDeliveryTestsConstants.BucketId)); + } + + [Test] + public void ValidateEntryId_ValidEntryId_DoesNotThrowException() + { + Assert.DoesNotThrow( + () => m_ContentDeliveryValidator.ValidateEntryId(CloudContentDeliveryTestsConstants.EntryId)); + } + + [Test] + public void ValidateEntryId_InvalidEntryId_ThrowsArgumentException() + { + Assert.Throws(() => m_ContentDeliveryValidator.ValidateEntryId("")); + } + + [Test] + public void ValidatePath_ValidPath_DoesNotThrowException() + { + const string path = "folder/file.jpg"; + Assert.DoesNotThrow(() => m_ContentDeliveryValidator.ValidatePath(path)); + } + + [Test] + public void ValidatePath_InvalidPath_ThrowsArgumentException() + { + string? path = null; + Assert.Throws(() => m_ContentDeliveryValidator.ValidatePath(path)); + } + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/EntryClientTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/EntryClientTests.cs new file mode 100644 index 0000000..d1b45fb --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/EntryClientTests.cs @@ -0,0 +1,519 @@ +using System.IO.Abstractions; +using System.IO.Abstractions.TestingHelpers; +using System.Net; +using Moq; +using NUnit.Framework; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Cli.ServiceAccountAuthentication; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Api; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; +using Configuration = Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client.Configuration; + +namespace CloudContentDeliveryTest.Service; + +[TestFixture] +public class EntryClientTests +{ + readonly Mock m_AuthServiceMock = new(); + readonly Mock m_EntriesApiMock = new(); + readonly Mock m_ContentApiMock = new(); + readonly Mock m_ContentDeliveryValidator = new(); + readonly Mock m_HttpClient = new(); + readonly Mock m_UploadContentClient = new(); + + IFileSystem m_FileSystemMock = null!; + EntryClient m_EntryClient = null!; + + const string k_TestAccessToken = "test-token"; + + [SetUp] + public void SetUp() + { + m_FileSystemMock = new MockFileSystem( + new Dictionary + { + { + @"c:\folder\file.jpg", new MockFileData( + new byte[] + { + 0x12, + 0x34, + 0x56, + 0xd2 + }) + }, + { @"local-folder/file1.txt", new MockFileData("Content of file 1") }, + { @"local-folder/file2.txt", new MockFileData("Content of file 2") } + }); + + m_AuthServiceMock.Reset(); + m_EntriesApiMock.Reset(); + m_ContentApiMock.Reset(); + m_HttpClient.Reset(); + m_UploadContentClient.Reset(); + m_EntriesApiMock.Setup(api => api.Configuration).Returns(new Configuration()); + m_ContentApiMock.Setup(api => api.Configuration).Returns(new Configuration()); + + m_AuthServiceMock.Setup(a => a.GetAccessTokenAsync(CancellationToken.None)) + .Returns(Task.FromResult(k_TestAccessToken)); + + m_EntryClient = new EntryClient( + m_AuthServiceMock.Object, + m_ContentDeliveryValidator.Object, + m_EntriesApiMock.Object, + m_ContentApiMock.Object, + m_UploadContentClient.Object, + m_FileSystemMock); + + m_UploadContentClient.Setup(client => client.GetContentType(It.IsAny())).Returns("type"); + m_UploadContentClient.Setup(client => client.GetContentSize(It.IsAny())).Returns(7); + m_UploadContentClient.Setup(client => client.GetContentHash(It.IsAny())) + .Returns("hash"); + + m_EntriesApiMock.Setup( + x => x.GetEntryByPathEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.ProjectId, + It.IsAny(), + 0, + It.IsAny() + )) + .ReturnsAsync( + new CcdCreateOrUpdateEntryBatch200ResponseInner + { + Entryid = new Guid(CloudContentDeliveryTestsConstants.EntryId), + SignedUrl = "url" + }); + } + + [Test] + public async Task AuthorizeServiceAsync() + { + await m_EntryClient.AuthorizeServiceAsync(CancellationToken.None); + m_AuthServiceMock.Verify(a => a.GetAccessTokenAsync(CancellationToken.None), Times.Once); + Assert.Multiple( + () => + { + Assert.That( + m_EntriesApiMock.Object.Configuration.DefaultHeaders["Authorization"], + Is.EqualTo($"Basic {k_TestAccessToken}")); + }); + } + + [Test] + public async Task UpdateEntryAsync_Success() + { + var expectedResponse = new CcdCreateOrUpdateEntryBatch200ResponseInner + { + Entryid = new Guid(CloudContentDeliveryTestsConstants.EntryId) + }; + + m_EntriesApiMock.Setup( + api => api.UpdateEntryEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.EntryId, + CloudContentDeliveryTestsConstants.ProjectId, + It.IsAny(), + 0, + CancellationToken.None)) + .ReturnsAsync(expectedResponse); + + var result = await m_EntryClient.UpdateEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.VersionId, + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata); + + m_EntriesApiMock.Verify( + api => api.UpdateEntryEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.EntryId, + CloudContentDeliveryTestsConstants.ProjectId, + It.IsAny(), + 0, + CancellationToken.None), + Times.Once); + + Assert.IsNotNull(result); + Assert.That(result!.Entryid, Is.EqualTo(expectedResponse.Entryid)); + } + + [Test] + public void UpdateEntryAsync_ExceptionThrown() + { + + m_EntriesApiMock.Setup( + api => api.UpdateEntryEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.EntryId, + CloudContentDeliveryTestsConstants.ProjectId, + It.IsAny(), + 0, + CancellationToken.None)) + .ThrowsAsync(new Exception("Error Updating Entry")); + + Assert.ThrowsAsync( + async () => + { + await m_EntryClient.UpdateEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.VersionId, + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata); + }); + + } + + [Test] + public async Task GetEntryAsync_Success() + { + + var result = await m_EntryClient.GetEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.VersionId, + CancellationToken.None); + + m_EntriesApiMock.Verify( + api => api.GetEntryByPathEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.VersionId, + 0, + CancellationToken.None), + Times.Once); + + Assert.IsNotNull(result); + Assert.That(result!.Entryid, Is.EqualTo(new Guid(CloudContentDeliveryTestsConstants.EntryId))); + } + + [Test] + public async Task DeleteEntryAsync_Success() + { + m_EntriesApiMock.Setup( + api => api.DeleteEntryEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.EntryId, + CloudContentDeliveryTestsConstants.ProjectId, + 0, + CancellationToken.None)); + + var result = await m_EntryClient.DeleteEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path); + + m_EntriesApiMock.Verify( + api => api.DeleteEntryEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.EntryId, + CloudContentDeliveryTestsConstants.ProjectId, + 0, + CancellationToken.None), + Times.Once); + + Assert.That(result, Is.EqualTo("Entry Deleted.")); + } + + [Test] + public async Task CopyEntryAsync_Success() + { + var expectedResponse = new CcdCreateOrUpdateEntryBatch200ResponseInner + { + CurrentVersionid = new Guid(CloudContentDeliveryTestsConstants.VersionId), + Entryid = new Guid(CloudContentDeliveryTestsConstants.EntryId), + SignedUrl = "url" + }; + + m_EntriesApiMock.Setup( + api => api.CreateOrUpdateEntryByPathEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + "local-folder/file1.txt", + CloudContentDeliveryTestsConstants.ProjectId, + It.IsAny(), + true, + 0, + CancellationToken.None)) + .ReturnsAsync(expectedResponse); + + m_EntriesApiMock.Setup( + x => x.GetEntryByPathEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + "local-folder/file1.txt", + CloudContentDeliveryTestsConstants.ProjectId, + It.IsAny(), + 0, + It.IsAny() + )) + .ReturnsAsync( + new CcdCreateOrUpdateEntryBatch200ResponseInner + { + CurrentVersionid = new Guid(CloudContentDeliveryTestsConstants.VersionId), + Entryid = new Guid(CloudContentDeliveryTestsConstants.EntryId) + }); + + m_UploadContentClient.Setup( + client => client.UploadContentToCcd( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK)); + + var result = await m_EntryClient.CopyEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + "local-folder/file1.txt", + "local-folder/file1.txt", + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata); + + m_EntriesApiMock.Verify( + api => api.CreateOrUpdateEntryByPathEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + "local-folder/file1.txt", + CloudContentDeliveryTestsConstants.ProjectId, + It.IsAny(), + true, + 0, + CancellationToken.None), + Times.Once); + + Assert.Multiple( + () => + { + Assert.That(result, Is.Not.Null); + Assert.That(result!.Entryid, Is.EqualTo(expectedResponse.Entryid)); + Assert.That(result.CurrentVersionid, Is.EqualTo(expectedResponse.CurrentVersionid)); + }); + } + + [Test] + public Task CopyEntryAsync_UploadFails_ThrowsException() + { + var expectedResponse = new CcdCreateOrUpdateEntryBatch200ResponseInner + { + CurrentVersionid = new Guid(CloudContentDeliveryTestsConstants.VersionId), + Entryid = new Guid(CloudContentDeliveryTestsConstants.EntryId), + SignedUrl = "url" + }; + + m_EntriesApiMock.Setup( + api => api.CreateOrUpdateEntryByPathEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + "local-folder/file1.txt", + CloudContentDeliveryTestsConstants.ProjectId, + It.IsAny(), + true, + 0, + CancellationToken.None)) + .ReturnsAsync(expectedResponse); + + m_UploadContentClient.Setup( + client => client.UploadContentToCcd( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.BadRequest)); + + Assert.ThrowsAsync( + () => m_EntryClient.CopyEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + "local-folder/file1.txt", + "local-folder/file1.txt", + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata)); + return Task.CompletedTask; + } + + [Test] + public async Task DownloadEntryAsync_Success_ReturnsContentStream() + { + var responseEntry = new CcdCreateOrUpdateEntryBatch200ResponseInner + { + Entryid = new Guid() + }; + m_EntriesApiMock.Setup( + api => api.GetEntryByPathEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.VersionId, + It.IsAny(), + CancellationToken.None)) + .ReturnsAsync(responseEntry); + + var expectedStream = m_FileSystemMock.File.OpenRead(@"local-folder/file1.txt"); + m_ContentApiMock.Setup( + api => api.GetContentEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + It.IsAny(), + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.VersionId, + It.IsAny(), + It.IsAny())) + .ReturnsAsync(expectedStream); + + var result = await m_EntryClient.DownloadEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.VersionId, + CancellationToken.None); + + Assert.That(result, Is.EqualTo(expectedStream)); + } + + [Test] + public void DownloadEntryAsync_ApiFailure_ThrowsException() + { + var responseEntry = new CcdCreateOrUpdateEntryBatch200ResponseInner + { + Entryid = new Guid() + }; + m_EntriesApiMock.Setup( + api => api.GetEntryByPathEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.VersionId, + It.IsAny(), + CancellationToken.None)) + .ReturnsAsync(responseEntry); + + m_ContentApiMock.Setup( + api => api.GetContentEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + It.IsAny(), + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.VersionId, + It.IsAny(), + It.IsAny())) + .ThrowsAsync(new Exception("API failure")); + + Assert.ThrowsAsync( + () => m_EntryClient.DownloadEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.VersionId, + CancellationToken.None)); + } + + [Test] + public void CopyEntryAsync_InvalidPath_ThrowsException() + { + + Assert.ThrowsAsync( + async () => + await m_EntryClient.CopyEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + "", + "", + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata)); + } + + [Test] + public async Task ListEntryAsync_Success() + { + + var expectedResponse = new ApiResponse>( + HttpStatusCode.OK, + new Multimap(), + new List + { + new(), + new() + } + ); + + m_EntriesApiMock.Setup( + api => api.GetEntriesEnvWithHttpInfoAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.ProjectId, + 1, + new Guid(CloudContentDeliveryTestsConstants.StartingAfter), + 10, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.Label, + CloudContentDeliveryTestsConstants.ContentType, + CloudContentDeliveryTestsConstants.Complete, + CloudContentDeliveryTestsConstants.SortBy, + CloudContentDeliveryTestsConstants.SortOrder, + 0, + CancellationToken.None)) + .ReturnsAsync(expectedResponse); + + var result = await m_EntryClient.ListEntryAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + 1, + CloudContentDeliveryTestsConstants.StartingAfter, + 10, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.Label, + CloudContentDeliveryTestsConstants.ContentType, + CloudContentDeliveryTestsConstants.Complete, + CloudContentDeliveryTestsConstants.SortBy, + CloudContentDeliveryTestsConstants.SortOrder, + CancellationToken.None); + + m_EntriesApiMock.Verify( + api => api.GetEntriesEnvWithHttpInfoAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.ProjectId, + 1, + new Guid(CloudContentDeliveryTestsConstants.StartingAfter), + 10, + CloudContentDeliveryTestsConstants.Path, + CloudContentDeliveryTestsConstants.Label, + CloudContentDeliveryTestsConstants.ContentType, + CloudContentDeliveryTestsConstants.Complete, + CloudContentDeliveryTestsConstants.SortBy, + CloudContentDeliveryTestsConstants.SortOrder, + 0, + CancellationToken.None), + Times.Once); + + Assert.IsNotNull(result); + Assert.That(result.Data.Count(), Is.EqualTo(2)); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/ReleaseClientTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/ReleaseClientTests.cs new file mode 100644 index 0000000..6770b2a --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/ReleaseClientTests.cs @@ -0,0 +1,221 @@ +using System.Net; +using Moq; +using NUnit.Framework; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.ServiceAccountAuthentication; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Api; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace CloudContentDeliveryTest.Service; + +[TestFixture] +public class ReleaseClientTests +{ + readonly Mock m_AuthServiceMock = new(); + readonly Mock m_ReleasesApiMock = new(); + readonly Mock m_ContentDeliveryValidatorMock = new(); + ReleaseClient m_ReleaseClient = null!; + const string k_TestAccessToken = "test-token"; + + [SetUp] + public void SetUp() + { + m_AuthServiceMock.Reset(); + m_ReleasesApiMock.Reset(); + m_ContentDeliveryValidatorMock.Reset(); + + m_ReleasesApiMock.Setup(api => api.Configuration).Returns(new Configuration()); + m_AuthServiceMock.Setup(a => a.GetAccessTokenAsync(CancellationToken.None)) + .Returns(Task.FromResult(k_TestAccessToken)); + + m_ReleaseClient = new ReleaseClient( + m_AuthServiceMock.Object, + m_ReleasesApiMock.Object, + m_ContentDeliveryValidatorMock.Object); + + m_ReleasesApiMock.Setup( + x => x.GetReleasesEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.ProjectId, + 1, + 1, + CloudContentDeliveryTestsConstants.ReleaseNumber.ToString(), + null, + null, + null, + null, + null, + null, + 0, + It.IsAny() + )) + .ReturnsAsync( + new List + { + new() + { + Releasenum = CloudContentDeliveryTestsConstants.ReleaseNumber, + Releaseid = new Guid(CloudContentDeliveryTestsConstants.ReleaseId) + } + }); + + } + + [Test] + public async Task CreateReleaseAsync_ValidParameters_ReturnsRelease() + { + var expectedReleaseResponse = new CcdGetBucket200ResponseLastRelease(); + m_ReleasesApiMock.Setup( + api => api.CreateReleaseEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.ProjectId, + It.IsAny(), + 0, + CancellationToken.None)) + .ReturnsAsync(expectedReleaseResponse); + var result = await m_ReleaseClient.CreateReleaseAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + It.IsAny(), + CancellationToken.None); + Assert.That(result, Is.EqualTo(expectedReleaseResponse)); + } + + [Test] + public async Task UpdateReleaseAsync_ValidParameters_ReturnsRelease() + { + var expectedReleaseResponse = new CcdGetBucket200ResponseLastRelease(); + + m_ReleasesApiMock.Setup( + api => api.UpdateReleaseEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.ReleaseId, + CloudContentDeliveryTestsConstants.ProjectId, + It.IsAny(), + 0, + CancellationToken.None)) + .ReturnsAsync(expectedReleaseResponse); + var result = await m_ReleaseClient.UpdateReleaseAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.ReleaseNumber, + CloudContentDeliveryTestsConstants.Notes, + CancellationToken.None); + Assert.That(result, Is.EqualTo(expectedReleaseResponse)); + } + + [Test] + public async Task GetReleaseAsync_ValidParameters_ReturnsRelease() + { + var expectedReleaseResponse = new CcdGetBucket200ResponseLastRelease(); + + m_ReleasesApiMock.Setup( + api => api.GetReleaseEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.ReleaseId, + CloudContentDeliveryTestsConstants.ProjectId, + 0, + CancellationToken.None)) + .ReturnsAsync(expectedReleaseResponse); + var result = await m_ReleaseClient.GetReleaseAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.ReleaseNumber, + CancellationToken.None); + Assert.That(result, Is.EqualTo(expectedReleaseResponse)); + } + + [Test] + public async Task GetReleaseByBadgeNameAsync_ValidParameters_ReturnsRelease() + { + var expectedReleaseResponse = new CcdGetBucket200ResponseLastRelease(); + m_ReleasesApiMock.Setup( + api => api.GetReleaseByBadgeEnvAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.BadgeName, + CloudContentDeliveryTestsConstants.ProjectId, + 0, + CancellationToken.None)) + .ReturnsAsync(expectedReleaseResponse); + var result = await m_ReleaseClient.GetReleaseByBadgeNameAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.BadgeName, + CancellationToken.None); + Assert.That(result, Is.EqualTo(expectedReleaseResponse)); + } + + [Test] + public async Task ListReleaseAsync_ValidParameters_ReturnsReleases() + { + var expectedReleaseList = new ApiResponse>( + HttpStatusCode.OK, + new Multimap(), + new List + { + new(), + new() + } + ); + + m_ReleasesApiMock.Setup( + api => api.GetReleasesEnvWithHttpInfoAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.Page, + CloudContentDeliveryTestsConstants.PerPage, + CloudContentDeliveryTestsConstants.ReleaseNumber.ToString(), + CloudContentDeliveryTestsConstants.Notes, + CloudContentDeliveryTestsConstants.PromotedFromBucket, + CloudContentDeliveryTestsConstants.PromotedFromRelease, + CloudContentDeliveryTestsConstants.BadgeName, + CloudContentDeliveryTestsConstants.SortBy, + CloudContentDeliveryTestsConstants.SortOrder, + 0, + CancellationToken.None)) + .ReturnsAsync(expectedReleaseList); + + var result = await m_ReleaseClient.ListReleaseAsync( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.Page, + CloudContentDeliveryTestsConstants.PerPage, + CloudContentDeliveryTestsConstants.ReleaseNumber.ToString(), + CloudContentDeliveryTestsConstants.PromotedFromBucket, + CloudContentDeliveryTestsConstants.PromotedFromRelease, + CloudContentDeliveryTestsConstants.BadgeName, + CloudContentDeliveryTestsConstants.Notes, + CloudContentDeliveryTestsConstants.SortBy, + CloudContentDeliveryTestsConstants.SortOrder, + CancellationToken.None); + + Assert.IsNotNull(result.Data); + Assert.That(result.Data.Count, Is.EqualTo(expectedReleaseList.Data.Count)); + } + + [Test] + public async Task AuthorizeServiceAsync() + { + await m_ReleaseClient.AuthorizeServiceAsync(CancellationToken.None); + m_AuthServiceMock.Verify(a => a.GetAccessTokenAsync(CancellationToken.None), Times.Once); + Assert.Multiple( + () => + { + Assert.That( + m_ReleasesApiMock.Object.Configuration.DefaultHeaders["Authorization"], + Is.EqualTo($"Basic {k_TestAccessToken}")); + }); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/SynchronizationServiceTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/SynchronizationServiceTests.cs new file mode 100644 index 0000000..11061f9 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/SynchronizationServiceTests.cs @@ -0,0 +1,530 @@ +using System.Diagnostics; +using System.IO.Abstractions; +using System.IO.Abstractions.TestingHelpers; +using System.Net; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Cli.ServiceAccountAuthentication; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Api; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace CloudContentDeliveryTest.Service; + +[TestFixture] +public class SynchronizationServiceTests +{ + SynchronizationService m_SyncService = null!; + readonly Mock m_EntriesApiMock = new(); + readonly Mock m_UploadContentClientMock = new(); + IFileSystem m_FileSystemMock = null!; + readonly Mock m_AuthServiceMock = new(); + readonly Mock m_ContentValidatorMock = new(); + readonly Mock> m_LoggerMock = new(); + const string k_TestAccessToken = "test-token"; + const string k_ContentHash = "0e5a0d2fa6bbd6e7b5b2a8337ebb4733"; + const long k_ContentSize = 7; + const string k_ContentType = "type"; + + [SetUp] + public void Setup() + { + + var file1 = new MockFileData("Content of file 1"); + var file2 = new MockFileData("Content of file 2"); + var file3 = new MockFileData("Content of file 3"); + + var fileSystemEntries = new Dictionary + { + { @"local-folder/file1.txt", file1 }, + { @"local-folder/file2.txt", file2 }, + { @"local-folder/file3.txt", file3 }, + { + @"images/image1.png", new MockFileData( + new byte[] + { + 0x89, + 0x50, + 0x4E, + 0x47 + }) + }, + { + @"data/binaryFile.bin", new MockFileData( + new byte[] + { + 0x00, + 0x01, + 0x02, + 0x03, + 0x04 + }) + } + }; + + m_FileSystemMock = new MockFileSystem(fileSystemEntries); + m_AuthServiceMock.Reset(); + m_EntriesApiMock.Reset(); + + m_EntriesApiMock.Setup(api => api.Configuration).Returns(new Configuration()); + m_AuthServiceMock.Setup(a => a.GetAccessTokenAsync(CancellationToken.None)) + .Returns(Task.FromResult(k_TestAccessToken)); + var headers = new Multimap(); + headers.Add("Content-Range", "items 1-10/500"); + headers.Add("unity-ratelimit", "limit=40,remaining=39,reset=1;limit=100000,remaining=99999,reset=1800"); + + + var ccdCreateOrUpdateList = new List(); + ccdCreateOrUpdateList.Add(new CcdCreateOrUpdateEntryBatch200ResponseInner { SignedUrl = "url", Path = "file1.txt" }); + m_EntriesApiMock.Setup( + api => api.CreateOrUpdateEntryBatchEnvWithHttpInfoAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync( + new ApiResponse>( + HttpStatusCode.OK, + headers, + ccdCreateOrUpdateList) + ); + + m_EntriesApiMock.Setup( + api => api.DeleteEntryBatchEnvWithHttpInfoAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync( + new ApiResponse( + HttpStatusCode.NoContent, + headers, + new object()) + ); + + m_UploadContentClientMock.Setup(client => client.GetContentType(It.IsAny())).Returns(k_ContentType); + m_UploadContentClientMock.Setup(client => client.GetContentSize(It.IsAny())) + .Returns(k_ContentSize); + m_UploadContentClientMock.Setup(client => client.GetContentHash(It.IsAny())) + .Returns(k_ContentHash); + + m_SyncService = new SynchronizationService( + m_EntriesApiMock.Object, + m_UploadContentClientMock.Object, + m_FileSystemMock, + m_AuthServiceMock.Object, + m_ContentValidatorMock.Object); + } + + [Test] + public async Task CalculateSynchronization_ValidInput_ReturnsSyncResult() + { + var headers = new Multimap(); + headers.Add("Content-Range", "items 1-10/500"); + headers.Add("unity-ratelimit", "limit=40,remaining=39,reset=1;limit=100000,remaining=99999,reset=1800"); + var expectedEntriesList = new ApiResponse>( + HttpStatusCode.OK, + headers, + new List()); + + m_EntriesApiMock.Setup( + api => api.GetEntriesEnvWithHttpInfoAsync( + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + CloudContentDeliveryTestsConstants.ProjectId, + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + CancellationToken.None)) + .ReturnsAsync(expectedEntriesList); + + var result = await m_SyncService.CalculateSynchronization( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + ".", + "", + false, + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata, + CancellationToken.None); + + Assert.IsNotNull(result); + Assert.That(result.EntriesToAdd.Count, Is.EqualTo(5)); + Assert.That(result.EntriesToUpdate, Is.Not.Null.And.Empty); + Assert.That(result.EntriesToDelete, Is.Not.Null.And.Empty); + Assert.That(result.EntriesToSkip, Is.Not.Null.And.Empty); + } + + [Test] + public void CalculateUploadSpeed_ValidInput_ReturnsUploadSpeed() + { + var result = SynchronizationService.CalculateUploadSpeed(60, 60); + Assert.That(result, Is.EqualTo(8)); + } + + [Test] + public void CalculateOperationSummary_ValidInput_ReturnsOperationSummary() + { + var syncResult = new SyncResult( + ); + var syncProcessTime = 2.2; + + var result = SynchronizationService.CalculateOperationSummary( + true, + syncResult, + syncProcessTime, + null, + null); + + Assert.IsNotNull(result); + Assert.IsTrue(result.OperationCompletedSuccessfully); + } + + [Test] + public void GetFilesFromDir_InvalidDirectory_ThrowsCliException() + { + var directoryPath = "test-directory"; + var exclusionPattern = ".jpg"; + Assert.Throws(() => m_SyncService.GetFilesFromDir(directoryPath, exclusionPattern)); + } + + [Test] + public void GetFilesFromDir_ValidInput_ReturnsEmptyFileSet() + { + var directoryPath = "local-folder"; + var exclusionPattern = ".txt"; + + var result = m_SyncService.GetFilesFromDir(directoryPath, exclusionPattern); + Assert.That(result, Is.Empty); + } + + [Test] + public void GetFilesFromDir_ValidInput_ReturnsFileSet() + { + var directoryPath = "local-folder"; + var exclusionPattern = ".jpg"; + + var result = m_SyncService.GetFilesFromDir(directoryPath, exclusionPattern); + + Assert.That(result, Has.Count.EqualTo(3)); + } + + [Test] + public async Task AuthorizeServiceAsync_ValidInput_AuthorizesService() + { + m_AuthServiceMock.Setup(a => a.GetAccessTokenAsync(CancellationToken.None)).ReturnsAsync("test-token"); + await m_SyncService.AuthorizeServiceAsync(CancellationToken.None); + m_AuthServiceMock.Verify(a => a.GetAccessTokenAsync(CancellationToken.None), Times.Once); + Assert.That( + m_EntriesApiMock.Object.Configuration.DefaultHeaders["Authorization"], + Is.EqualTo("Basic test-token")); + } + + [Test] + public void CalculateDifference_ValidInput_FileAdded() + { + var localFiles = new HashSet + { + "file1.txt" + }; + var remoteEntries = new List(); + + var result = m_SyncService.CalculateDifference( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + "local-folder", + true, + remoteEntries, + localFiles, + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata + ); + Assert.That(result.EntriesToAdd.Count, Is.EqualTo(1), "EntriesToAdd is equal to 1"); + } + + [Test] + public void CalculateDifference_ValidInput_FileSkipped() + { + var localFiles = new HashSet + { + "file1.txt" + }; + var remoteEntries = new List + { + new() + { + Path = "file1.txt", + ContentSize = k_ContentSize, + ContentType = k_ContentType, + ContentHash = k_ContentHash, + Entryid = new Guid(), + CurrentVersionid = new Guid() + } + }; + + var result = m_SyncService.CalculateDifference( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + "local-folder", + true, + remoteEntries, + localFiles, + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata + ); + Assert.That(result.EntriesToSkip.Count, Is.EqualTo(1), "Skip is equal to 1"); + } + + [Test] + public void CalculateDifference_ValidInput_FileUpdated() + { + var localFiles = new HashSet + { + "file1.txt" + }; + var remoteEntries = new List + { + new() + { + Path = "file1.txt", + ContentSize = 52, + ContentType = k_ContentType, + ContentHash = "d3a1d1c92b50629f1b9253a32979b682", + Entryid = new Guid(), + CurrentVersionid = new Guid() + } + }; + + var result = m_SyncService.CalculateDifference( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + "local-folder", + false, + remoteEntries, + localFiles, + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata + ); + Assert.That(result.EntriesToUpdate.Count, Is.EqualTo(1), "Update is equal to 1"); + } + + [Test] + public void CalculateDifference_ValidInput_FileDeleted() + { + var localFiles = new HashSet(); + var remoteEntries = new List + { + new() + { + Path = "file1.txt", + ContentSize = k_ContentSize, + ContentType = k_ContentType, + ContentHash = k_ContentHash, + Entryid = new Guid(), + CurrentVersionid = new Guid() + } + }; + + var result = m_SyncService.CalculateDifference( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + "local-folder", + true, + remoteEntries, + localFiles, + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata + ); + Assert.That(result.EntriesToDelete.Count, Is.EqualTo(1), "Delete is equal to 1"); + } + + [Test] + public async Task ProcessSynchronization_SuccessfulSynchronization_ReturnsReleaseEntries() + { + m_UploadContentClientMock.Setup( + client => client.UploadContentToCcd( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK)); + + var syncResult = m_SyncService.CalculateDifference( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + "local-folder", + true, + new List + { + new() + { + Entryid = new Guid(), + Path = "file2.txt", + ContentHash = "e1b1ec4ec8057de9b2a72c38ba0305c5", + ContentSize = 8, + ContentType = "text/plain", + Complete = true + }, + new() + { + Entryid = new Guid(), + Path = "file3.txt", + ContentSize = k_ContentSize, + ContentType = k_ContentType, + ContentHash = k_ContentHash, + Complete = true + }, + new() + { + Entryid = new Guid(), + Path = "file5.txt", + ContentHash = "h5gf1ec4ec457de9b2k34c38ba0305c5", + ContentSize = 8, + ContentType = "text/plain", + Complete = true + } + }, + new HashSet + { + "file1.txt", + "file2.txt", + "file3.txt" + }, + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata + ); + + Assert.That(syncResult.EntriesToAdd.Count, Is.EqualTo(1), "Add is equal to 1"); + Assert.That(syncResult.EntriesToDelete.Count, Is.EqualTo(1), "Delete is equal to 1"); + Assert.That(syncResult.EntriesToUpdate.Count, Is.EqualTo(1), "Update is equal to 1"); + Assert.That(syncResult.EntriesToSkip.Count, Is.EqualTo(1), "Skip is equal to 1"); + + var result = await m_SyncService.ProcessSynchronization( + m_LoggerMock.Object, + true, + syncResult, + "local-folder", + 3, + 5, + 1000, + CancellationToken.None); + + Assert.That(result.Count, Is.EqualTo(3), "process result is of size 3"); + + } + + [Test] + public void ProcessSynchronization_TaskThrowsException_ThrowsCliException() + { + var syncResult = m_SyncService.CalculateDifference( + CloudContentDeliveryTestsConstants.ProjectId, + CloudContentDeliveryTestsConstants.EnvironmentId, + CloudContentDeliveryTestsConstants.BucketId, + "local-folder", + false, + new List(), + new HashSet + { + "file1.txt", + "file2.txt" + }, + CloudContentDeliveryTestsConstants.Labels, + CloudContentDeliveryTestsConstants.Metadata + ); + + m_UploadContentClientMock.Setup( + client => client.UploadContentToCcd( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.BadRequest)); + + Assert.ThrowsAsync( + () => m_SyncService.ProcessSynchronization( + m_LoggerMock.Object, + true, + syncResult, + "local-folder", + 3, + 5, + 1000, + CancellationToken.None)); + } + + [Test] + public async Task ShouldNotDelayWhenRateLimitNotExceeded() + { + var headers = new Multimap + { + { "unity-ratelimit", "limit=40,remaining=39,reset=1" } + }; + var rateLimitStatus = new SharedRateLimitStatus(); + var cancellationToken = new CancellationToken(false); + + await SynchronizationService.RespectRateLimitAsync(headers, rateLimitStatus, cancellationToken); + + Assert.IsFalse(rateLimitStatus.IsRateLimited); + Assert.That(rateLimitStatus.ResetTime, Is.EqualTo(TimeSpan.Zero), "Reset time is equal to 0"); + + } + + [Test] + public async Task ShouldDelayWhenRateLimitExceeded() + { + var headers = new Multimap + { + { "unity-ratelimit", "limit=40,remaining=0,reset=1" } + }; + var rateLimitStatus = new SharedRateLimitStatus(); + var cancellationToken = new CancellationToken(false); + var stopwatch = Stopwatch.StartNew(); + await SynchronizationService.RespectRateLimitAsync(headers, rateLimitStatus, cancellationToken); + Assert.IsTrue(stopwatch.ElapsedMilliseconds > 700); + stopwatch.Stop(); + Assert.IsFalse(rateLimitStatus.IsRateLimited); + Assert.That(rateLimitStatus.ResetTime, Is.EqualTo(TimeSpan.Zero), "Reset time is equal to 0"); + + } + + [Test] + public void ShouldUpdateRateLimitStatusBeforeDelayOnRateLimitExceeded() + { + var headers = new Multimap + { + { "unity-ratelimit", "limit=40,remaining=0,reset=60" } + }; + var rateLimitStatus = new SharedRateLimitStatus(); + +#pragma warning disable CS4014 // Because we don't want to await + SynchronizationService.RespectRateLimitAsync(headers, rateLimitStatus, CancellationToken.None); +#pragma warning restore CS4014 + + Assert.IsTrue(rateLimitStatus.IsRateLimited); + Assert.That( + rateLimitStatus.ResetTime, + Is.EqualTo(TimeSpan.FromSeconds(60)), + "Reset time is equal to 60 seconds"); + + } + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/UploadContentClientTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/UploadContentClientTests.cs new file mode 100644 index 0000000..394a5ba --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Service/UploadContentClientTests.cs @@ -0,0 +1,91 @@ +using System.IO.Abstractions.TestingHelpers; +using System.Net; +using Moq; +using Moq.Protected; +using NUnit.Framework; +using Unity.Services.Cli.CloudContentDelivery.Service; + +namespace CloudContentDeliveryTest.Service; + +[TestFixture] +public class UploadContentClientTests +{ + UploadContentClient m_UploadContentClient = null!; + readonly Mock m_MessageHandlerMock = new(MockBehavior.Strict); + MockFileSystem m_FileSystemMock = null!; + + [SetUp] + public void SetUp() + { + + var file1 = new MockFileData("Content of file 1"); + var file2 = new MockFileData("Content of file 2"); + + var fileSystemEntries = new Dictionary + { + { @"local-folder/file1.txt", file1 }, + { @"local-folder/file2.txt", file2 } + }; + + m_FileSystemMock = new MockFileSystem(fileSystemEntries); + var httpClient = new HttpClient(m_MessageHandlerMock.Object); + m_UploadContentClient = new UploadContentClient(httpClient); + } + + [Test] + public async Task UploadContentToCcd_Success_ReturnsSuccessfulResponse() + { + var httpClientMock = new Mock(); + var mockResponse = new HttpResponseMessage(HttpStatusCode.OK); + httpClientMock + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(mockResponse); + + var httpClient = new HttpClient(httpClientMock.Object); + var uploadClient = new UploadContentClient(httpClient); + await using var fileStream = m_FileSystemMock.File.OpenRead(@"local-folder/file1.txt"); + var response = await uploadClient.UploadContentToCcd("https://www.unity.com/fakesignedurl/upload", fileStream); + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + } + + [Test] + public async Task UploadContentToCcd_Failure_ReturnsErrorResponse() + { + var httpClientMock = new Mock(); + var mockResponse = new HttpResponseMessage(HttpStatusCode.BadRequest); + httpClientMock + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(mockResponse); + + await using var fileStream = m_FileSystemMock.File.OpenRead(@"local-folder/file1.txt"); + var httpClient = new HttpClient(httpClientMock.Object); + var uploadClient = new UploadContentClient(httpClient); + var response = await uploadClient.UploadContentToCcd("https://www.unity.com/fakesignedurl/upload", fileStream); + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public void GetContentType_ShouldReturnContentType() + { + var localPath = "example.txt"; + var localPath2 = "example.jpg"; + var localPath3 = "example.bin"; + + var result = m_UploadContentClient.GetContentType(localPath); + var result2 = m_UploadContentClient.GetContentType(localPath2); + var result3 = m_UploadContentClient.GetContentType(localPath3); + + Assert.That(result, Is.EqualTo("text/plain")); + Assert.That(result2, Is.EqualTo("image/jpeg")); + Assert.That(result3, Is.EqualTo("application/octet-stream")); + } + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Unity.Services.Cli.CloudContentDelivery.UnitTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Unity.Services.Cli.CloudContentDelivery.UnitTest.csproj new file mode 100644 index 0000000..54c831e --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Unity.Services.Cli.CloudContentDelivery.UnitTest.csproj @@ -0,0 +1,26 @@ + + + net8.0 + enable + enable + false + CloudContentDeliveryTest + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + \ No newline at end of file diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Utils/CcdUtilsTests.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Utils/CcdUtilsTests.cs new file mode 100644 index 0000000..24dc199 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery.UnitTest/Utils/CcdUtilsTests.cs @@ -0,0 +1,106 @@ +using System.Net; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using Unity.Services.Cli.CloudContentDelivery.Utils; +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace CloudContentDeliveryTest.Utils; + +[TestFixture] +public class CcdUtilsTests +{ + ApiResponse> m_ValidApiResponse = null!; + ApiResponse> m_MissingContentRangeApiResponse = null!; + + [SetUp] + public void SetUp() + { + var validHeaders = new Multimap + { + { "Content-Range", "1-10/100" } + }; + + m_ValidApiResponse = new ApiResponse>( + HttpStatusCode.OK, + validHeaders, + new List() + ); + + m_MissingContentRangeApiResponse = new ApiResponse>( + HttpStatusCode.OK, + new Multimap(), + new List() + ); + } + + [Test] + public void GetPaginationInformation_ShouldReturnCorrectString() + { + var result = CcdUtils.GetPaginationInformation(m_ValidApiResponse); + Assert.That(result, Is.EqualTo("Listing 1-10/100")); + } + + [Test] + public void GetPaginationInformation_WithApiResponseMissingContentRange_ShouldReturnNull() + { + var result = CcdUtils.GetPaginationInformation(m_MissingContentRangeApiResponse); + Assert.That(result, Is.Null); + } + + [Test] + public void ValidateBucketIdIsPresent_ValidGuid_DoesNotThrowException() + { + Assert.DoesNotThrow(() => CcdUtils.ValidateBucketIdIsPresent("00000000-0000-0000-0000-000000000000")); + } + + [Test] + public void ValidateBucketIdIsPresent_InvalidGuid_ThrowsException() + { + Assert.Throws(() => CcdUtils.ValidateBucketIdIsPresent("invalid-guid")); + } + + [Test] + public void ValidateBucketIdIsPresent_NullBucketId_ThrowsException() + { + Assert.Throws(() => CcdUtils.ValidateBucketIdIsPresent(null)); + } + + [Test] + public void ValidateBucketIdIsPresent_EmptyString_ThrowsException() + { + Assert.Throws(() => CcdUtils.ValidateBucketIdIsPresent(string.Empty)); + } + + [Test] + public void ValidateParseMetadata_Valid_ReturnJsonObject() + { + + var validMetadata = new + { + test = new + { + subkey = "value", + subkey2 = "value" + } + }; + + var validMetadataJson = JsonConvert.SerializeObject(validMetadata); + var expectedJObject = JObject.Parse(validMetadataJson); + var result = CcdUtils.ParseMetadata(validMetadataJson); + Assert.That(result, Is.EqualTo(expectedJObject), "The parsed metadata should match the expected JSON object."); + + result = CcdUtils.ParseMetadata(""); + Assert.That(result, Is.Null, "The result should be null when the input is an empty string."); + } + + [Test] + public void ValidateParseMetadata_InvalidJson_ThrowsException() + { + Assert.Throws(() => CcdUtils.ParseMetadata("{'partial'")); + Assert.Throws(() => CcdUtils.ParseMetadata("[invalid]")); + } + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/CloudContentDeliveryModule.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/CloudContentDeliveryModule.cs new file mode 100644 index 0000000..bed8df1 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/CloudContentDeliveryModule.cs @@ -0,0 +1,638 @@ +using System.CommandLine; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Badges; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Buckets; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Entries; +using Unity.Services.Cli.CloudContentDelivery.Handlers.Releases; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.IO; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Input; +using Unity.Services.Cli.Common.Networking; +using Unity.Services.Cli.Common.Utils; +using Unity.Services.Cli.Common.Validator; +using Unity.Services.CloudContentDelivery.Authoring.Core.IO; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Api; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; + +namespace Unity.Services.Cli.CloudContentDelivery; + +/// +/// A Template module to achieve a get request command: ugs cloudcontentdelivery get `address` -o `file` +/// +public class CloudContentDeliveryModule : ICommandModule +{ + public CloudContentDeliveryModule() + { + ModuleRootCommand = new Command("ccd", "Manage Cloud Content Delivery."); + RegisterModulesCommands(ModuleRootCommand); + } + + public Command? ModuleRootCommand { get; } + + /// + /// Register service to UGS CLI host builder + /// + /// + /// + public static void RegisterServices(HostBuilderContext hostBuilderContext, IServiceCollection serviceCollection) + { + var config = new Configuration + { + BasePath = EndpointHelper.GetCurrentEndpointFor(), + Timeout = 600000, + UserAgent = "ugs_cli/1.0.0" + }; + config.DefaultHeaders.SetXClientIdHeader(); + + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(new BadgesApi(config)); + serviceCollection.AddSingleton(new BucketsApi(config)); + serviceCollection.AddSingleton(new ReleasesApi(config)); + serviceCollection.AddSingleton(new EntriesApi(config)); + serviceCollection.AddSingleton(new PermissionsApi(config)); + serviceCollection.AddSingleton(new ContentApi(config)); + + serviceCollection.AddSingleton(serviceProvider => new ClientWrapper( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService())); + + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(new UploadContentClient(new HttpClient())); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton( + new ContentDeliveryValidator(new ConfigurationValidator())); + + serviceCollection.AddTransient(); + + /* + This will commented until we implement Deployment/Fetch + + // Registers services required for Deployment/Fetch + // Register the command handler + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient();*/ + } + + public static void RegisterModulesCommands(Command root) + { + var bucketHandlerCommand = CreateBucketHandlerCommand(); + var entryHandlerCommand = CreateEntryHandlerCommand(); + var releaseHandlerCommand = CreateReleaseHandlerCommand(); + var badgeHandlerCommand = CreateBadgeHandlerCommand(); + + root.Add(bucketHandlerCommand); + root.Add(entryHandlerCommand); + root.Add(releaseHandlerCommand); + root.Add(badgeHandlerCommand); + } + + static Command CreateBucketHandlerCommand() + { + var listBucketHandlerCommand = new Command( + "list", + "List buckets for a project.") + { + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.PageOption, + CloudContentDeliveryInput.PerPageOption, + CloudContentDeliveryInput.FilterNameOption, + CloudContentDeliveryInputBuckets.SortByBucketOption, + CloudContentDeliveryInput.SortOrderOption + }; + + listBucketHandlerCommand.SetHandler< + CloudContentDeliveryInputBuckets, + IUnityEnvironment, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + ListBucketHandler.ListAsync); + + var createBucketHandlerCommand = new Command( + "create", + "Create bucket for a project.") + { + CloudContentDeliveryInput.BucketNameArgument, + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInputBuckets.BucketDescriptionOption, + CloudContentDeliveryInputBuckets.BucketPrivateOption + }; + + createBucketHandlerCommand.SetHandler< + CloudContentDeliveryInputBuckets, + IUnityEnvironment, + BucketClient, + ILogger, + CancellationToken>( + CreateBucketHandler.CreateAsync); + + var deleteBucketHandlerCommand = new Command( + "delete", + "Delete buckets.") + { + CloudContentDeliveryInput.BucketNameArgument, + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption + }; + + deleteBucketHandlerCommand.SetHandler< + CloudContentDeliveryInput, + IUnityEnvironment, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + DeleteBucketHandler.DeleteAsync); + + var infoBucketHandlerCommand = new Command( + "info", + "Get bucket info.") + { + CloudContentDeliveryInput.BucketNameArgument, + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption + }; + + infoBucketHandlerCommand.SetHandler< + CloudContentDeliveryInputBuckets, + IUnityEnvironment, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + GetBucketHandler.GetAsync); + + var permissionsBucketUpdateHandlerCommand = new Command( + "update", + "Manage permissions for a bucket.") + { + CloudContentDeliveryInput.BucketNameArgument, + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInputBuckets.ActionOption, + CloudContentDeliveryInputBuckets.PermissionOption, + CloudContentDeliveryInputBuckets.RoleOption + }; + + permissionsBucketUpdateHandlerCommand.SetHandler< + CloudContentDeliveryInputBuckets, + IUnityEnvironment, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + PermissionBucketHandler.PermissionUpdateAsync); + + var permissionsBucketHandlerCommand = new Command( + "permissions", + "Manage permissions for a bucket.") + { + permissionsBucketUpdateHandlerCommand + }; + + var bucketHandlerCommand = new Command( + "buckets", + "Manage buckets for a project.") + { + listBucketHandlerCommand, + createBucketHandlerCommand, + deleteBucketHandlerCommand, + infoBucketHandlerCommand, + permissionsBucketHandlerCommand + }; + return bucketHandlerCommand; + } + + static Command CreateReleaseHandlerCommand() + { + var createReleaseHandlerCommand = new Command( + "create", + "Create release from latest version of current bucket.") + { + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption, + CloudContentDeliveryInput.NoteOption, + CloudContentDeliveryInput.ReleaseMetadataOption + }; + + createReleaseHandlerCommand.SetHandler< + CloudContentDeliveryInput, + IUnityEnvironment, + ReleaseClient, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + CreateReleaseHandler.CreateAsync); + + var infoReleaseHandlerCommand = new Command( + "info", + "Get release info for specific release.") + { + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption, + CloudContentDeliveryInput.ReleaseNumArgument + }; + + infoReleaseHandlerCommand.SetHandler< + CloudContentDeliveryInput, + IUnityEnvironment, + ReleaseClient, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + GetReleaseHandler.GetAsync); + var listReleaseHandlerCommand = new Command( + "list", + "List releases for current bucket.") + { + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption, + CloudContentDeliveryInput.PageOption, + CloudContentDeliveryInput.PerPageOption, + CloudContentDeliveryInput.ReleaseNumOpt, + CloudContentDeliveryInput.NoteOption, + CloudContentDeliveryInput.PromotedFromBucketOption, + CloudContentDeliveryInput.PromotedFromReleaseOption, + CloudContentDeliveryInput.BadgeOption, + CloudContentDeliveryInput.SortByReleaseOption, + CloudContentDeliveryInput.SortOrderOption + }; + + listReleaseHandlerCommand.SetHandler< + CloudContentDeliveryInput, + IUnityEnvironment, + ReleaseClient, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + ListReleaseHandler.ListAsync); + + var promoteReleaseHandlerCommand = new Command( + "promote", + "Promote release to another bucket.") + { + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption, + CloudContentDeliveryInput.ReleaseNumArgument, + CloudContentDeliveryInput.TargetBucketNameArgument, + CloudContentDeliveryInput.TargetEnvironmentNameArgument, + CloudContentDeliveryInput.NoteOption + }; + + promoteReleaseHandlerCommand.SetHandler< + CloudContentDeliveryInputBuckets, + IUnityEnvironment, + ReleaseClient, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + PromoteBucketHandler.PromoteAsync); + + var promotionsStatusReleaseHandlerCommand = new Command( + "status", + "Check promotion status.") + { + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption, + CloudContentDeliveryInput.PromotionIdArgument + }; + var promotionsReleaseHandlerCommand = new Command( + "promotions", + "Manage promotions.") + { + promotionsStatusReleaseHandlerCommand + }; + + promotionsStatusReleaseHandlerCommand.SetHandler< + CloudContentDeliveryInputBuckets, + IUnityEnvironment, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + PromotionBucketHandler.PromotionStatusAsync); + + var updateReleaseHandlerCommand = new Command( + "update", + "Update an existing Release.") + { + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption, + CloudContentDeliveryInput.ReleaseNumArgument, + CloudContentDeliveryInput.NoteOptionRequired + }; + + updateReleaseHandlerCommand.SetHandler< + CloudContentDeliveryInput, + IUnityEnvironment, + ReleaseClient, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + UpdateReleaseHandler.UpdateAsync); + + var releaseHandlerCommand = new Command( + "releases", + "Manage releases for current bucket.") + { + createReleaseHandlerCommand, + infoReleaseHandlerCommand, + listReleaseHandlerCommand, + promoteReleaseHandlerCommand, + promotionsReleaseHandlerCommand, + updateReleaseHandlerCommand + }; + return releaseHandlerCommand; + } + + static Command CreateEntryHandlerCommand() + { + var copyEntryHandlerCommand = new Command( + "copy", + "Create entry for current bucket from a local file.") + { + CloudContentDeliveryInput.LocalPathArgument, + CloudContentDeliveryInput.RemotePathArgument, + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption, + CloudContentDeliveryInput.LabelsOption, + CloudContentDeliveryInput.MetadataOption + }; + + copyEntryHandlerCommand.SetHandler< + CloudContentDeliveryInput, + IUnityEnvironment, + EntryClient, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + CopyEntryHandler.CopyAsync); + + var deleteEntryHandlerCommand = new Command( + "delete", + "Delete entry from current bucket.") + { + CloudContentDeliveryInput.EntryPathArgument, + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption + }; + + deleteEntryHandlerCommand.SetHandler< + CloudContentDeliveryInput, + IUnityEnvironment, + EntryClient, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + DeleteEntryHandler.DeleteAsync); + + var downloadEntryHandlerCommand = new Command( + "download", + "Download entry content from current bucket.") + { + CloudContentDeliveryInput.EntryPathArgument, + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption, + CloudContentDeliveryInput.VersionIdOption + }; + + downloadEntryHandlerCommand.SetHandler< + CloudContentDeliveryInput, + IUnityEnvironment, + EntryClient, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + DownloadEntryHandler.DownloadAsync); + + var infoEntryHandlerCommand = new Command( + "info", + "Get entry info from current bucket.") + { + CloudContentDeliveryInput.EntryPathArgument, + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption, + CloudContentDeliveryInput.VersionIdOption + }; + + infoEntryHandlerCommand.SetHandler< + CloudContentDeliveryInput, + IUnityEnvironment, + EntryClient, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + GetEntryHandler.GetAsync); + + var listEntryHandlerCommand = new Command( + "list", + "List entries for current bucket.") + { + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption, + CloudContentDeliveryInput.PageOption, + CloudContentDeliveryInput.PerPageOption, + CloudContentDeliveryInput.SortByEntryOption, + CloudContentDeliveryInput.SortOrderOption, + CloudContentDeliveryInput.StartingAfterOption, + CloudContentDeliveryInput.PathOption, + CloudContentDeliveryInput.LabelOption, + CloudContentDeliveryInput.ContentTypeOption, + CloudContentDeliveryInput.CompleteOption + }; + + listEntryHandlerCommand.SetHandler< + CloudContentDeliveryInput, + IUnityEnvironment, + EntryClient, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + ListEntryHandler.ListAsync); + + var syncEntryHandlerCommand = new Command( + "sync", + "Sync entries from local directory for current bucket.\nAutomatically creates, updates, and deletes entries\nwithin the bucket to match the files in the local directory.") + { + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption, + CloudContentDeliveryInput.LocalFolderArgument, + CloudContentDeliveryInput.ExclusionPatternOption, + CloudContentDeliveryInput.DryRunOption, + CloudContentDeliveryInput.RetryOption, + CloudContentDeliveryInput.TimeoutOption, + CloudContentDeliveryInput.DeleteOption, + CloudContentDeliveryInput.LabelsOption, + CloudContentDeliveryInput.CreateReleaseOption, + CloudContentDeliveryInput.IncludeSyncEntriesOnlyOption, + CloudContentDeliveryInput.UpdateBadgeOption, + CloudContentDeliveryInput.SyncMetadataOption, + CloudContentDeliveryInput.ReleaseNotesOption, + CloudContentDeliveryInput.VerboseOption + }; + + + + syncEntryHandlerCommand.SetHandler< + CloudContentDeliveryInput, + IUnityEnvironment, + ClientWrapper, + SynchronizationService, + ILogger, + ILoadingIndicator, + CancellationToken>( + SyncEntryHandler.SyncEntriesAsync); + + var updateEntryHandlerCommand = new Command( + "update", + "Update entry for current bucket.") + { + CloudContentDeliveryInput.EntryPathArgument, + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption, + CloudContentDeliveryInput.LabelsOption, + CloudContentDeliveryInput.MetadataOption, + CloudContentDeliveryInput.VersionIdOption + }; + + updateEntryHandlerCommand.SetHandler< + CloudContentDeliveryInput, + IUnityEnvironment, + EntryClient, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + UpdateEntryHandler.UpdateAsync); + + var entryHandlerCommand = new Command( + "entries", + "Manage entries for current bucket.") + { + copyEntryHandlerCommand, + deleteEntryHandlerCommand, + downloadEntryHandlerCommand, + infoEntryHandlerCommand, + listEntryHandlerCommand, + syncEntryHandlerCommand, + updateEntryHandlerCommand + }; + return entryHandlerCommand; + } + + static Command CreateBadgeHandlerCommand() + { + var createBadgeHandlerCommand = new Command( + "create", + "Create a new badge or move an existing one") + { + CloudContentDeliveryInput.ReleaseNumArgument, + CloudContentDeliveryInput.BadgeNameArgument, + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption + }; + + createBadgeHandlerCommand.SetHandler< + CloudContentDeliveryInput, + IUnityEnvironment, + BadgeClient, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + CreateBadgeHandler.CreateAsync); + + var listBadgeHandlerCommand = new Command( + "list", + "List badges in the current bucket.") + { + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption, + CloudContentDeliveryInput.PageOption, + CloudContentDeliveryInput.PerPageOption, + CloudContentDeliveryInput.FilterNameOption, + CloudContentDeliveryInput.SortByBadgeOption, + CloudContentDeliveryInput.SortOrderOption + }; + + listBadgeHandlerCommand.SetHandler< + CloudContentDeliveryInput, + IUnityEnvironment, + BadgeClient, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + ListBadgeHandler.ListAsync); + + var deleteBadgeHandlerCommand = new Command( + "delete", + "Delete a badge.") + { + CommonInput.EnvironmentNameOption, + CommonInput.CloudProjectIdOption, + CloudContentDeliveryInput.BucketNameOption, + CloudContentDeliveryInput.BadgeNameArgument + }; + + deleteBadgeHandlerCommand.SetHandler< + CloudContentDeliveryInput, + IUnityEnvironment, + BadgeClient, + BucketClient, + ILogger, + ILoadingIndicator, + CancellationToken>( + DeleteBadgeHandler.DeleteAsync); + + var badgeHandlerCommand = new Command( + "badges", + "Manage badges for a release.") + { + createBadgeHandlerCommand, + listBadgeHandlerCommand, + deleteBadgeHandlerCommand + }; + return badgeHandlerCommand; + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Exceptions/CloudContentDeliveryException.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Exceptions/CloudContentDeliveryException.cs new file mode 100644 index 0000000..f585a0c --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Exceptions/CloudContentDeliveryException.cs @@ -0,0 +1,25 @@ +using System.Runtime.Serialization; +using Unity.Services.Cli.Common.Exceptions; + +namespace Unity.Services.Cli.CloudContentDelivery.Exceptions; + +/// +/// Example of custom exception for incorrect user operation. +/// +public class CloudContentDeliveryException : CliException +{ + public CloudContentDeliveryException(int exitCode = Common.Exceptions.ExitCode.HandledError) + : base(exitCode) { } + + /// + /// constructor. + /// + /// A message with instructions to guide user how to fix the operation. + /// Exit code when this exception triggered. Default value is HandledError. + public CloudContentDeliveryException(string message, int exitCode = Common.Exceptions.ExitCode.HandledError) + : base(message, exitCode) { } + + public CloudContentDeliveryException( + string message, Exception innerException, int exitCode = Common.Exceptions.ExitCode.HandledError) + : base(message, innerException, exitCode) { } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Fetch/CloudContentDeliveryFetchService.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Fetch/CloudContentDeliveryFetchService.cs new file mode 100644 index 0000000..6f81c57 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Fetch/CloudContentDeliveryFetchService.cs @@ -0,0 +1,78 @@ +using Spectre.Console; +using Unity.Services.Cli.Authoring.Input; +using Unity.Services.Cli.Authoring.Service; +using Unity.Services.CloudContentDelivery.Authoring.Core.Fetch; +using Unity.Services.CloudContentDelivery.Authoring.Core.Model; +using Unity.Services.CloudContentDelivery.Authoring.Core.Service; +using FetchResult = Unity.Services.Cli.Authoring.Model.FetchResult; + +namespace Unity.Services.Cli.CloudContentDelivery.Fetch; + +class CloudContentDeliveryFetchService : IFetchService +{ + readonly ICloudContentDeliveryFetchHandler m_FetchHandler; + readonly ICloudContentDeliveryClient m_Client; + const string k_DeployFileExtension = ".ccd"; + const string k_ServiceType = "CloudContentDelivery"; + + public CloudContentDeliveryFetchService( + ICloudContentDeliveryFetchHandler fetchHandler, + ICloudContentDeliveryClient client) + { + m_FetchHandler = fetchHandler; + m_Client = client; + } + + public string ServiceType => k_ServiceType; + public string ServiceName { get; } = k_ServiceType; + + public IReadOnlyList FileExtensions => new[] + { + k_DeployFileExtension + }; + + public async Task FetchAsync( + FetchInput input, + IReadOnlyList filePaths, + string projectId, + string environmentId, + StatusContext? loadingContext, + CancellationToken cancellationToken) + { + m_Client.Initialize(environmentId, projectId, cancellationToken); + loadingContext?.Status($"Reading {k_ServiceType} files..."); + var resources = await GetResourcesFromFiles(filePaths); + + loadingContext?.Status($"Fetching {k_ServiceType} files..."); + var res = await m_FetchHandler.FetchAsync( + input.Path, + resources, + input.DryRun, + input.Reconcile, + cancellationToken); + + return new FetchResult( + res.Updated, + res.Deleted, + res.Created, + res.Fetched, + res.Failed, + input.DryRun + ); + } + + static async Task> GetResourcesFromFiles(IReadOnlyList filePaths) + { + var resources = await Task.WhenAll(filePaths.Select(GetResourcesFromFile)); + + return resources.SelectMany(r => r).ToList(); + } + + static Task> GetResourcesFromFile(string filePath) + { + // A file may contain more than one resource if the resources are small + throw new NotImplementedException(); + } + + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Badges/CreateBadgeHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Badges/CreateBadgeHandler.cs new file mode 100644 index 0000000..a4b6aad --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Badges/CreateBadgeHandler.cs @@ -0,0 +1,67 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.CloudContentDelivery.Utils; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Logging; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Badges; + +static class CreateBadgeHandler +{ + public static async Task CreateAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IBadgeClient badgeClient, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Creating badge...", + _ => CreateAsync( + input, + unityEnvironment, + badgeClient, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task CreateAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IBadgeClient badgeClient, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var bucketName = input.BucketNameOpt!; + var badgeName = input.BadgeName!; + var releaseNum = (int)input.ReleaseNum!; + + var bucketId = await bucketClient.GetBucketIdByName( + projectId, + environmentId, + bucketName, + cancellationToken); + + CcdUtils.ValidateBucketIdIsPresent(bucketId); + + var result = await badgeClient.CreateBadgeAsync( + projectId, + environmentId, + bucketId, + badgeName, + releaseNum, + cancellationToken); + + logger.LogResultValue(new BadgeResult(result)); + } + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Badges/DeleteBadgeHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Badges/DeleteBadgeHandler.cs new file mode 100644 index 0000000..44e0170 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Badges/DeleteBadgeHandler.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.CloudContentDelivery.Utils; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Badges; + +static class DeleteBadgeHandler +{ + public static async Task DeleteAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IBadgeClient badgeClient, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Deleting badge...", + _ => DeleteAsync( + input, + unityEnvironment, + badgeClient, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task DeleteAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IBadgeClient badgeClient, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var badgeName = input.BadgeName!; + var bucketName = input.BucketNameOpt!; + + var bucketId = await bucketClient.GetBucketIdByName( + projectId, + environmentId, + bucketName, + cancellationToken); + + CcdUtils.ValidateBucketIdIsPresent(bucketId); + + await badgeClient.DeleteBadgeAsync( + projectId, + environmentId, + bucketId, + badgeName, + cancellationToken); + logger.LogInformation("Badge {badgeName} deleted.", badgeName); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Badges/ListBadgeHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Badges/ListBadgeHandler.cs new file mode 100644 index 0000000..7f4de65 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Badges/ListBadgeHandler.cs @@ -0,0 +1,80 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.CloudContentDelivery.Utils; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Logging; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Badges; + +static class ListBadgeHandler +{ + public static async Task ListAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IBadgeClient badgeClient, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Fetching badges list...", + _ => ListAsync( + input, + unityEnvironment, + badgeClient, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task ListAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IBadgeClient badgeClient, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var currentPage = input.Page!; + var perPage = input.PerPage!; + var bucketName = input.BucketNameOpt!; + var releaseNum = input.ReleaseNumOption!; + var filterName = input.FilterName!; + var sortBy = input.SortByBadge!; + var sortOrder = input.SortOrder!; + + var bucketId = await bucketClient.GetBucketIdByName( + projectId, + environmentId, + bucketName, + cancellationToken); + + CcdUtils.ValidateBucketIdIsPresent(bucketId); + + var result = await badgeClient.ListBadgeAsync( + projectId, + environmentId, + bucketId, + currentPage, + perPage, + filterName, + releaseNum, + sortBy, + sortOrder, + cancellationToken); + + var paginationInformation = CcdUtils.GetPaginationInformation(result); + if (paginationInformation != null) logger.LogInformation(paginationInformation); + var badgeList = result.Data + .Select( + e => new ListBadgeResult(e.Releaseid.ToString(), e.Releasenum, e.Name)); + logger.LogResultValue(badgeList); + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/CreateBucketHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/CreateBucketHandler.cs new file mode 100644 index 0000000..e35cc8f --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/CreateBucketHandler.cs @@ -0,0 +1,54 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Logging; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Buckets; + +static class CreateBucketHandler +{ + public static async Task CreateAsync( + CloudContentDeliveryInputBuckets input, + IUnityEnvironment unityEnvironment, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Creating bucket...", + _ => CreateAsync( + input, + unityEnvironment, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task CreateAsync( + CloudContentDeliveryInputBuckets input, + IUnityEnvironment unityEnvironment, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var name = input.BucketName!; + var description = input.BucketDescription ?? ""; + var privateBucket = input.BucketPrivate ?? false; + + var result = await bucketClient.CreateBucketAsync( + projectId, + environmentId, + name, + description, + privateBucket, + cancellationToken); + + logger.LogResultValue(new BucketResult(result)); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/DeleteBucketHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/DeleteBucketHandler.cs new file mode 100644 index 0000000..8caf1b1 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/DeleteBucketHandler.cs @@ -0,0 +1,47 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Logging; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Buckets; + +static class DeleteBucketHandler +{ + public static async Task DeleteAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Deleting bucket...", + _ => DeleteAsync( + input, + unityEnvironment, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task DeleteAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var bucketName = input.BucketName!; + var result = await bucketClient.DeleteBucketAsync( + projectId, + environmentId, + bucketName, + cancellationToken); + logger.LogResultValue(result); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/GetBucketHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/GetBucketHandler.cs new file mode 100644 index 0000000..d8e7704 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/GetBucketHandler.cs @@ -0,0 +1,49 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Logging; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Buckets; + +static class GetBucketHandler +{ + public static async Task GetAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Fetching bucket info...", + _ => GetAsync( + input, + unityEnvironment, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task GetAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var bucketName = input.BucketName!; + + var result = await bucketClient.GetBucketAsync( + projectId, + environmentId, + bucketName, + cancellationToken); + logger.LogResultValue(new BucketResult(result)); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/ListBucketHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/ListBucketHandler.cs new file mode 100644 index 0000000..467dc15 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/ListBucketHandler.cs @@ -0,0 +1,66 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.CloudContentDelivery.Utils; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Logging; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Buckets; + +static class ListBucketHandler +{ + public static async Task ListAsync( + CloudContentDeliveryInputBuckets input, + IUnityEnvironment unityEnvironment, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Fetching buckets list...", + _ => ListAsync( + input, + unityEnvironment, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task ListAsync( + CloudContentDeliveryInputBuckets input, + IUnityEnvironment unityEnvironment, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var currentPage = input.Page!; + var perPage = input.PerPage!; + var filterName = input.FilterName!; + var sortBy = input.SortByBucket!; + var sortOrder = input.SortOrder!; + var description = input.BucketDescription!; + var result = await bucketClient.ListBucketAsync( + projectId, + environmentId, + currentPage, + perPage, + filterName, + description, + sortBy, + sortOrder, + cancellationToken); + + var paginationInformation = CcdUtils.GetPaginationInformation(result); + if (paginationInformation != null) logger.LogInformation(paginationInformation); + + var bucketsList = result.Data + .Select( + e => new ListBucketResult(e.Id.ToString(), e.Name)); + logger.LogResultValue(bucketsList); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/PermissionBucketHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/PermissionBucketHandler.cs new file mode 100644 index 0000000..0e9b018 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/PermissionBucketHandler.cs @@ -0,0 +1,53 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Logging; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Buckets; + +static class PermissionBucketHandler +{ + public static async Task PermissionUpdateAsync( + CloudContentDeliveryInputBuckets input, + IUnityEnvironment unityEnvironment, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Updating permissions...", + _ => PermissionUpdateAsync( + input, + unityEnvironment, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task PermissionUpdateAsync( + CloudContentDeliveryInputBuckets input, + IUnityEnvironment unityEnvironment, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var bucketName = input.BucketName!; + + var result = await bucketClient.UpdatePermissionBucketAsync( + projectId, + environmentId, + bucketName, + input.Action, + input.Permission, + input.Role, + cancellationToken); + logger.LogResultValue(new PermissionResult(result)); + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/PromoteBucketHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/PromoteBucketHandler.cs new file mode 100644 index 0000000..82a141c --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/PromoteBucketHandler.cs @@ -0,0 +1,79 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Logging; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Buckets; + +static class PromoteBucketHandler +{ + public static async Task PromoteAsync( + CloudContentDeliveryInputBuckets input, + IUnityEnvironment unityEnvironment, + IReleaseClient releaseClient, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Promoting bucket...", + _ => PromoteAsync( + input, + unityEnvironment, + releaseClient, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task PromoteAsync( + CloudContentDeliveryInputBuckets input, + IUnityEnvironment unityEnvironment, + IReleaseClient releaseClient, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var bucketName = input.BucketNameOpt!; + var fromReleaseNum = (int)input.ReleaseNum!; + var notes = input.Notes!; + var targetBucketName = input.TargetBucketName!; + var toEnvironment = await unityEnvironment.FetchIdentifierFromSpecificEnvironmentNameAsync(input.TargetEnvironment!, cancellationToken); + + var bucketId = await bucketClient.GetBucketIdByName( + projectId, + environmentId, + bucketName, + cancellationToken); + + var toBucket = await bucketClient.GetBucketIdByName( + projectId, + toEnvironment, + targetBucketName, + cancellationToken); + + var fromReleaseId = await releaseClient.GetReleaseIdByNumber( + projectId, + environmentId, + bucketId, + fromReleaseNum, + cancellationToken); + + var result = await bucketClient.PromoteBucketEnvAsync( + projectId, + environmentId, + bucketId, + fromReleaseId, + notes, + toBucket, + toEnvironment, + cancellationToken); + logger.LogResultValue(new PromoteResult(result)); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/PromotionBucketHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/PromotionBucketHandler.cs new file mode 100644 index 0000000..bc3c75b --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Buckets/PromotionBucketHandler.cs @@ -0,0 +1,57 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Logging; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Buckets; + +static class PromotionBucketHandler +{ + public static async Task PromotionStatusAsync( + CloudContentDeliveryInputBuckets input, + IUnityEnvironment unityEnvironment, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Fetching promotion status...", + _ => PromotionStatusAsync( + input, + unityEnvironment, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task PromotionStatusAsync( + CloudContentDeliveryInputBuckets input, + IUnityEnvironment unityEnvironment, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var promotionId = input.PromotionId!; + var bucketName = input.BucketNameOpt!; + + var bucketId = await bucketClient.GetBucketIdByName( + projectId, + environmentId, + bucketName, + cancellationToken); + + var result = await bucketClient.GetPromotionAsync( + projectId, + environmentId, + bucketId, + promotionId, + cancellationToken); + logger.LogResultValue(new PromotionResult(result)); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/CopyEntryHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/CopyEntryHandler.cs new file mode 100644 index 0000000..fec3f7f --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/CopyEntryHandler.cs @@ -0,0 +1,70 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.CloudContentDelivery.Utils; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Logging; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Entries; + +static class CopyEntryHandler +{ + public static async Task CopyAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IEntryClient entryClient, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Copying entry...", + _ => CopyAsync( + input, + unityEnvironment, + entryClient, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task CopyAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IEntryClient entryClient, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var bucketName = input.BucketNameOpt!; + var localPath = input.LocalPath!; + var remotePath = input.RemotePath!; + var labels = input.Labels!; + var metadata = input.Metadata!; + + var bucketId = await bucketClient.GetBucketIdByName( + projectId, + environmentId, + bucketName, + cancellationToken); + CcdUtils.ValidateBucketIdIsPresent(bucketId); + + var result = await entryClient.CopyEntryAsync( + projectId, + environmentId, + bucketId, + localPath, + remotePath, + labels, + metadata, + cancellationToken); + + logger.LogResultValue(new EntryResult(result)); + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/DeleteEntryHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/DeleteEntryHandler.cs new file mode 100644 index 0000000..11173a5 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/DeleteEntryHandler.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.CloudContentDelivery.Utils; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Entries; + +static class DeleteEntryHandler +{ + public static async Task DeleteAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IEntryClient entryClient, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Deleting entry...", + _ => DeleteAsync( + input, + unityEnvironment, + entryClient, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task DeleteAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IEntryClient entryClient, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var bucketName = input.BucketNameOpt!; + var entryPath = input.EntryPath!; + + var bucketId = await bucketClient.GetBucketIdByName( + projectId, + environmentId, + bucketName, + cancellationToken); + + CcdUtils.ValidateBucketIdIsPresent(bucketId); + + var result = await entryClient.DeleteEntryAsync( + projectId, + environmentId, + bucketId, + entryPath, + cancellationToken); + logger.LogInformation(result); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/DownloadEntryHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/DownloadEntryHandler.cs new file mode 100644 index 0000000..67b65ff --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/DownloadEntryHandler.cs @@ -0,0 +1,78 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.CloudContentDelivery.Utils; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Entries; + +static class DownloadEntryHandler +{ + const int k_BufferSize = 8192; + + public static async Task DownloadAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IEntryClient entryClient, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Downloading entry content...", + _ => DownloadAsync( + input, + unityEnvironment, + entryClient, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task DownloadAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IEntryClient entryClient, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var bucketName = input.BucketNameOpt!; + var path = input.EntryPath!; + var versionId = input.VersionId!; + + var bucketId = await bucketClient.GetBucketIdByName( + projectId, + environmentId, + bucketName, + cancellationToken); + + CcdUtils.ValidateBucketIdIsPresent(bucketId); + + var result = await entryClient.DownloadEntryAsync( + projectId, + environmentId, + bucketId, + path, + versionId, + cancellationToken); + + FileStream fileStream; + await using (fileStream = new FileStream( + Path.GetFileName(path), + FileMode.Create, + FileAccess.Write, + FileShare.None, + k_BufferSize, + true)) + { + await result.CopyToAsync(fileStream, cancellationToken); + fileStream.Close(); + } + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/GetEntryHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/GetEntryHandler.cs new file mode 100644 index 0000000..27976c1 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/GetEntryHandler.cs @@ -0,0 +1,65 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.CloudContentDelivery.Utils; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Logging; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Entries; + +static class GetEntryHandler +{ + public static async Task GetAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IEntryClient entryClient, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Getting entry...", + _ => GetAsync( + input, + unityEnvironment, + entryClient, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task GetAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IEntryClient entryClient, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var bucketName = input.BucketNameOpt!; + var entryPath = input.EntryPath!; + var versionId = input.VersionId!; + + var bucketId = await bucketClient.GetBucketIdByName( + projectId, + environmentId, + bucketName, + cancellationToken); + + CcdUtils.ValidateBucketIdIsPresent(bucketId); + + var result = await entryClient.GetEntryAsync( + projectId, + environmentId, + bucketId, + entryPath, + versionId, + cancellationToken); + logger.LogResultValue(new EntryResult(result)); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/ListEntryHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/ListEntryHandler.cs new file mode 100644 index 0000000..f7a68e4 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/ListEntryHandler.cs @@ -0,0 +1,87 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.CloudContentDelivery.Utils; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Logging; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Entries; + +static class ListEntryHandler +{ + public static async Task ListAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IEntryClient entryClient, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "List entries...", + _ => ListAsync( + input, + unityEnvironment, + entryClient, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task ListAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IEntryClient entryClient, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var bucketName = input.BucketNameOpt!; + var currentPage = input.Page!; + var perPage = input.PerPage!; + var sortBy = input.SortByEntry!; + var sortOrder = input.SortOrder!; + var startingAfter = input.StartingAfter!; + var path = input.Path!; + var label = input.Label!; + var contentType = input.ContentType!; + bool? complete = input.Complete == true ? true : null; + + var bucketId = await bucketClient.GetBucketIdByName( + projectId, + environmentId, + bucketName, + cancellationToken); + + CcdUtils.ValidateBucketIdIsPresent(bucketId); + + var result = await entryClient.ListEntryAsync( + projectId, + environmentId, + bucketId, + currentPage, + startingAfter, + perPage, + path, + label, + contentType, + complete, + sortBy, + sortOrder, + cancellationToken); + + var paginationInformation = CcdUtils.GetPaginationInformation(result); + if (paginationInformation != null) logger.LogInformation(paginationInformation); + var entryList = result.Data + .Select( + e => new ListEntryResult(e.Entryid.ToString(), e.Path)); + + logger.LogResultValue(entryList); + + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/SyncEntryHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/SyncEntryHandler.cs new file mode 100644 index 0000000..c5568c5 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/SyncEntryHandler.cs @@ -0,0 +1,168 @@ +using System.Diagnostics; +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.CloudContentDelivery.Utils; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Logging; +using Unity.Services.Cli.Common.Utils; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Entries; + +static class SyncEntryHandler +{ + public static async Task SyncEntriesAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IClientWrapper clients, + ISynchronizationService synchronizationService, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Syncing entries...", + async _ => await SyncEntriesAsync( + input, + unityEnvironment, + clients, + synchronizationService, + logger, + cancellationToken)); + } + + internal static async Task SyncEntriesAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IClientWrapper clients, + ISynchronizationService synchronizationService, + ILogger logger, + CancellationToken cancellationToken) + { + const int maxConcurrentRequests = 5; + const int retryDelayMilliseconds = 5000; + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var bucketName = input.BucketNameOpt!; + var localFolder = input.LocalFolder!; + var exclusionPattern = input.ExclusionPattern!; + var delete = input.Delete == true; + var retryCount = input.Retry ?? 3; + var dryRun = (bool)input.DryRun!; + var badgeName = input.UpdateBadge; + var createRelease = input.CreateRelease == true; + var includeSyncEntriesOnly = input.IncludeSyncEntriesOnly ?? true; + var labels = input.Labels; + var releaseNotes = input.ReleaseNotes!; + var verbose = input.Verbose == true; + + HandleBadgeAndReleaseNoteWarning(logger, createRelease, badgeName, + releaseNotes); + + var bucketId = await clients.BucketClient!.GetBucketIdByName( + projectId, + environmentId, + bucketName, + cancellationToken); + + CcdUtils.ValidateBucketIdIsPresent(bucketId); + + var stopwatch = Stopwatch.StartNew(); + var newCancellationToken = cancellationToken; + var metadata = input.SyncMetadata != null ? CcdUtils.ParseMetadata(input.SyncMetadata) : null; + + if (input.Timeout != 0) + { + var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(input.Timeout)); + newCancellationToken = cancellationTokenSource.Token; + } + + var syncResult = await synchronizationService.CalculateSynchronization( + projectId, + environmentId, + bucketId, + localFolder, + exclusionPattern, + delete, + labels, + metadata, + newCancellationToken); + stopwatch.Stop(); + stopwatch.Restart(); + + if (!dryRun) + { + var releaseEntries = await synchronizationService.ProcessSynchronization( + logger, + verbose, + syncResult, + localFolder, + retryCount, + maxConcurrentRequests, + retryDelayMilliseconds, + cancellationToken); + stopwatch.Stop(); + var syncProcessTime = stopwatch.Elapsed.TotalSeconds; + CcdGetBucket200ResponseLastRelease? release = null; + CcdGetBucket200ResponseLastReleaseBadgesInner? badge = null; + if (createRelease) + { + + var releaseRequest = new CcdCreateReleaseRequest() + { + Notes = releaseNotes + }; + + if (includeSyncEntriesOnly) + releaseRequest.Entries = releaseEntries; + + if (metadata != null) + releaseRequest.Metadata = metadata; + + release = await clients.ReleaseClient!.CreateReleaseAsync( + projectId, + environmentId, + bucketId, + releaseRequest, + newCancellationToken); + + if (badgeName != null) + badge = await clients.BadgeClient!.CreateBadgeAsync( + projectId, + environmentId, + bucketId, + badgeName, + release.Releasenum, + newCancellationToken); + + } + + logger.LogResultValue( + SynchronizationService.CalculateOperationSummary( + verbose, + syncResult, + syncProcessTime, + release, + badge)); + } + else + { + logger.LogResultValue(syncResult); + } + + } + + static void HandleBadgeAndReleaseNoteWarning(ILogger logger, bool createRelease, string? badgeName, string releaseNotes) + { + if (createRelease) return; + if (badgeName != null) + logger.LogWarning( + "The badge option requires the 'create release' option to be set to true. As a result, no badge was created or updated."); + + if (!string.IsNullOrEmpty(releaseNotes)) + logger.LogWarning( + "The release notes option requires the 'create release' option to be set to true. As a result, no release notes were added."); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/UpdateEntryHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/UpdateEntryHandler.cs new file mode 100644 index 0000000..a532816 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Handlers/Entries/UpdateEntryHandler.cs @@ -0,0 +1,69 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Input; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Cli.CloudContentDelivery.Utils; +using Unity.Services.Cli.Common.Console; +using Unity.Services.Cli.Common.Logging; +using Unity.Services.Cli.Common.Utils; + +namespace Unity.Services.Cli.CloudContentDelivery.Handlers.Entries; + +static class UpdateEntryHandler +{ + public static async Task UpdateAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IEntryClient entryClient, + IBucketClient bucketClient, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) + { + await loadingIndicator.StartLoadingAsync( + "Updating Entry...", + _ => UpdateAsync( + input, + unityEnvironment, + entryClient, + bucketClient, + logger, + cancellationToken)); + } + + internal static async Task UpdateAsync( + CloudContentDeliveryInput input, + IUnityEnvironment unityEnvironment, + IEntryClient entryClient, + IBucketClient bucketClient, + ILogger logger, + CancellationToken cancellationToken) + { + var environmentId = await unityEnvironment.FetchIdentifierAsync(cancellationToken); + var projectId = input.CloudProjectId!; + var bucketName = input.BucketNameOpt!; + var entryPath = input.EntryPath!; + var versionId = input.VersionId!; + var labels = input.Labels!; + var metadata = input.Metadata!; + + var bucketId = await bucketClient.GetBucketIdByName( + projectId, + environmentId, + bucketName, + cancellationToken); + + CcdUtils.ValidateBucketIdIsPresent(bucketId); + + var result = await entryClient.UpdateEntryAsync( + projectId, + environmentId, + bucketId, + entryPath, + versionId, + labels, + metadata, + cancellationToken); + logger.LogResultValue(new EntryResult(result)); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/IO/FileSystem.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/IO/FileSystem.cs new file mode 100644 index 0000000..6dcf233 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/IO/FileSystem.cs @@ -0,0 +1,7 @@ +using Unity.Services.CloudContentDelivery.Authoring.Core.IO; + +namespace Unity.Services.Cli.CloudContentDelivery.IO; + +class FileSystem : Common.IO.FileSystem, IFileSystem +{ +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Input/CloudContentDeliveryInput.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Input/CloudContentDeliveryInput.cs new file mode 100644 index 0000000..d2f5436 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Input/CloudContentDeliveryInput.cs @@ -0,0 +1,487 @@ +using System.CommandLine; +using Unity.Services.Cli.Common.Input; +using Unity.Services.Cli.Common.Models; +using Unity.Services.Cli.Common.SystemEnvironment; + +namespace Unity.Services.Cli.CloudContentDelivery.Input; + +public class CloudContentDeliveryInput : CommonInput +{ + + public static readonly Argument AddressArgument = new( + "address", + "The address to send GET request"); + + public static readonly Argument LocalFolderArgument = new( + "local-directory-path", + "The local directory path to synchronize"); + + public static readonly Argument EntryPathArgument = new( + "entry-path", + "The path of the entry"); + + public static readonly Option OutputFileOption = new( + new[] + { + "-o", + "--output" + }, + "Write output to file instead of stdout"); + + public static readonly Option DeleteOption = new( + new[] + { + "-x", + "--delete" + }, + "Entries that do not exist locally are deleted during sync."); + + public static readonly Option ExclusionPatternOption = new( + new[] + { + "-f", + "--exclude" + }, + "Exclude files and folders matching this pattern."); + + public static readonly Option CreateReleaseOption = new( + new[] + { + "-r", + "--create-release" + }, + "Creates a release containing the files that were synced."); + + public static readonly Option VerboseOption = new( + new[] + { + "-v", + "--verbose" + }, + "Turn on verbose mode for more synchronization details."); + + public static readonly Option UpdateBadgeOption = new( + new[] + { + "-u", + "--badge" + }, + "If release flag is set, badge to be assigned to the release."); + + public static readonly Option ReleaseMetadataOption = new( + new[] + { + "-m", + "--release-metadata" + }, + "JSON metadata associated with the release."); + + public static readonly Option SyncMetadataOption = new( + new[] + { + "-m", + "--metadata" + }, + "JSON metadata associated with the entries and release."); + + public static readonly Option ReleaseNotesOption = new( + new[] + { + "-n", + "--release-notes" + }, + "If release flag is set, notes associated with the release."); + + public static readonly Option IncludeSyncEntriesOnlyOption = new( + new[] + { + "-i", + "--include-entries-added-during-sync" + }, + "Including entries added to the bucket during the ongoing synchronization process to ensure all new additions are captured in the release."); + + public static readonly Option DryRunOption = new( + new[] + { + "-d", + "--dry-run" + }, + "Prints the operations that would be performed without actually executing them."); + + public static readonly Option RetryOption = new( + new[] + { + "-z", + "--retry" + }, + "Number of times to retry syncing a file (default is 3)."); + + public static readonly Option TimeoutOption = new( + new[] + { + "-t", + "--timeout" + }, + "Upload timeout in seconds (default is no timeout)."); + + public static readonly Argument LocalPathArgument = new("local-path", "Local file path."); + + public static readonly Argument RemotePathArgument = new("remote-path", "Remote file path."); + + public static readonly Argument BadgeNameArgument = new("badge-name", "Name of the badge."); + + public static readonly Argument BucketNameArgument = new("bucket-name", "Name of the bucket."); + + public static readonly Argument ReleaseNumArgument = new("release-num", "Release number."); + + public static readonly Argument EntryIdArgument = new("entry-id", "Entry id."); + + public static readonly Option BucketNameOption = new( + new[] + { + "-b", + "--bucket-name" + }, + "Name of the bucket."); + + public static readonly Option MetadataOption = new( + new[] + { + "-m", + "--metadata" + }, + "JSON metadata associated with this entry."); + + public static readonly Option PageOption = new( + new[] + { + "-pa", + "--page" + }, + "Current Page."); + + public static readonly Option PerPageOption = new( + new[] + { + "-pp", + "--per-page" + }, + "Items Per Page."); + + public static readonly Option FilterNameOption = new( + new[] + { + "-n", + "--filter-by-name" + }, + "Filter by name."); + + public static readonly Option SortOrderOption = new( + new[] + { + "-o", + "--sort-order" + }, + "Sort order (asc, desc)."); + + public static readonly Option SortByOption = new( + new[] + { + "-s", + "--sort-by" + }, + "Sort by values."); + + public static readonly Option SortByBadgeOption = new( + new[] + { + "-s", + "--sort-by" + }, + "Sort by values (name, releasenum, created)."); + + public static readonly Option SortByReleaseOption = new( + new[] + { + "-s", + "--sort-by" + }, + "Sort by values (releasenum, created)."); + + public static readonly Option SortByEntryOption = new( + new[] + { + "-s", + "--sort-by" + }, + "Sort by values (path, content_size, content_type, last_modified)."); + + public static readonly Option ReleaseNumOpt = new( + new[] + { + "-r", + "--release-number" + }, + "Release number."); + + public static readonly Option BadgeOption = new( + new[] + { + "-u", + "--badges" + }, + "Badges associated with the release."); + + public static readonly Option StartingAfterOption = new( + new[] + { + "-a", + "--starting-after" + }, + "Returns entries listed after the named entry id"); + + public static readonly Option PathOption = new( + new[] + { + "-t", + "--path" + }, + "The path of the entry."); + + public static readonly Option LabelOption = new( + new[] + { + "-l", + "--label" + }, + "The label of the entry."); + + public static readonly Option> LabelsOption = new( + new[] + { + "-l", + "--labels" + }, + "List of labels."); + + public static readonly Option ContentTypeOption = new( + new[] + { + "-c", + "--content-type" + }, + "The content type of the entry."); + + public static readonly Option CompleteOption = new( + new[] + { + "-w", + "--complete" + }, + "Is the content is uploaded or not."); + + public static readonly Option PromotedFromReleaseOption = new( + new[] + { + "-pr", + "--promoted-from-release" + }, + "The release where the promotion originated."); + + public static readonly Option PromotedFromBucketOption = new( + new[] + { + "-pb", + "--promoted-from-bucket" + }, + "The bucket from which the release was promoted."); + + public static readonly Option NoteOption = new( + new[] + { + "-n", + "--notes" + }, + "Notes associated with the release."); + + public static readonly Option VersionIdOption = new( + new[] + { + "-v", + "--version-id" + }, + "Version id of the entry."); + + public static readonly Option NoteOptionRequired = new( + new[] + { + "-n", + "--notes" + }, + "Notes associated with the release.") + { + IsRequired = true + }; + + public static readonly Argument TargetEnvironmentNameArgument = new( + "target-environment-name", + "Name of the target environment."); + + public static readonly Argument PromotionIdArgument = new("promotion-id", "Promotion id."); + + public static readonly Argument ReleaseIdArgument = new("release-id", "Release id."); + + public static readonly Argument TargetBucketNameArgument = new( + "target-bucket-name", + "Name of the target bucket."); + + [InputBinding(nameof(TargetEnvironmentNameArgument))] + public string? TargetEnvironment { get; set; } + + [InputBinding(nameof(PromotionIdArgument))] + public string? PromotionId { get; set; } + + [InputBinding(nameof(TargetBucketNameArgument))] + public string? TargetBucketName { get; set; } + + [EnvironmentBinding(Keys.EnvironmentKeys.BucketName)] + [ConfigBinding(Keys.ConfigKeys.BucketName)] + [InputBinding(nameof(BucketNameOption))] + public string? BucketNameOpt { get; set; } + + [InputBinding(nameof(AddressArgument))] + public string? Address { get; set; } + + [InputBinding(nameof(EntryPathArgument))] + public string? EntryPath { get; set; } + + [InputBinding(nameof(LocalFolderArgument))] + public string? LocalFolder { get; set; } + + [InputBinding(nameof(OutputFileOption))] + public string? OutputFile { get; set; } + + [InputBinding(nameof(DeleteOption))] + public bool? Delete { get; set; } + + [InputBinding(nameof(ExclusionPatternOption))] + public string? ExclusionPattern { get; set; } + + [InputBinding(nameof(DryRunOption))] + public bool? DryRun { get; set; } + + [InputBinding(nameof(IncludeSyncEntriesOnlyOption))] + public bool? IncludeSyncEntriesOnly { get; set; } + + [InputBinding(nameof(CreateReleaseOption))] + public bool? CreateRelease { get; set; } + + [InputBinding(nameof(UpdateBadgeOption))] + public string? UpdateBadge { get; set; } + + [InputBinding(nameof(VerboseOption))] + public bool? Verbose { get; set; } + + [InputBinding(nameof(ReleaseMetadataOption))] + public string? ReleaseMetadata { get; set; } + + [InputBinding(nameof(SyncMetadataOption))] + public string? SyncMetadata { get; set; } + + [InputBinding(nameof(ReleaseNotesOption))] + public string? ReleaseNotes { get; set; } + + [InputBinding(nameof(RetryOption))] + public int? Retry { get; set; } + + [InputBinding(nameof(TimeoutOption))] + public double Timeout { get; set; } + + [InputBinding(nameof(LocalPathArgument))] + public string? LocalPath { get; set; } + + [InputBinding(nameof(RemotePathArgument))] + public string? RemotePath { get; set; } + + [InputBinding(nameof(BadgeNameArgument))] + public string? BadgeName { get; set; } + + [InputBinding(nameof(BucketNameArgument))] + public string? BucketName { get; set; } + + [InputBinding(nameof(EntryIdArgument))] + public string? EntryId { get; set; } + + [InputBinding(nameof(VersionIdOption))] + public string? VersionId { get; set; } + + [InputBinding(nameof(MetadataOption))] + public string? Metadata { get; set; } + + [InputBinding(nameof(PageOption))] + public int? Page { get; set; } + + [InputBinding(nameof(PerPageOption))] + public int? PerPage { get; set; } + + [InputBinding(nameof(FilterNameOption))] + public string? FilterName { get; set; } + + [InputBinding(nameof(SortOrderOption))] + public string? SortOrder { get; set; } + + [InputBinding(nameof(SortByOption))] + public string? SortBy { get; set; } + + [InputBinding(nameof(SortByBadgeOption))] + public string? SortByBadge { get; set; } + + [InputBinding(nameof(SortByReleaseOption))] + public string? SortByRelease { get; set; } + + [InputBinding(nameof(SortByEntryOption))] + public string? SortByEntry { get; set; } + + [InputBinding(nameof(ReleaseNumOpt))] + public string? ReleaseNumOption { get; set; } + + [InputBinding(nameof(BadgeOption))] + public string? Badges { get; set; } + + [InputBinding(nameof(StartingAfterOption))] + public string? StartingAfter { get; set; } + + [InputBinding(nameof(PathOption))] + public string? Path { get; set; } + + [InputBinding(nameof(LabelOption))] + public string? Label { get; set; } + + [InputBinding(nameof(LabelsOption))] + public List? Labels { get; set; } + + [InputBinding(nameof(ContentTypeOption))] + public string? ContentType { get; set; } + + [InputBinding(nameof(CompleteOption))] + public bool? Complete { get; set; } + + [InputBinding(nameof(NoteOption))] + public string? Notes { get; set; } + + [InputBinding(nameof(NoteOptionRequired))] + public string? NotesRequired { get; set; } + + [InputBinding(nameof(ReleaseIdArgument))] + public string? ReleaseId { get; set; } + + [InputBinding(nameof(PromotedFromReleaseOption))] + public string? PromotedFromRelease { get; set; } + + [InputBinding(nameof(PromotedFromBucketOption))] + public string? PromotedFromBucket { get; set; } + + [InputBinding(nameof(ReleaseNumArgument))] + public int? ReleaseNum { get; set; } + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Input/CloudContentDeliveryInputBuckets.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Input/CloudContentDeliveryInputBuckets.cs new file mode 100644 index 0000000..cd10126 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Input/CloudContentDeliveryInputBuckets.cs @@ -0,0 +1,87 @@ +using System.CommandLine; +using Unity.Services.Cli.Common.Input; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace Unity.Services.Cli.CloudContentDelivery.Input; + +public class CloudContentDeliveryInputBuckets : CloudContentDeliveryInput +{ + + public static readonly Option RoleOption = new( + new[] + { + "-r", + "--role" + }, + "Sets the role (Client, User) affected by the permission change. User role can be changed for additional management permission (Write) and Client role can be changed to allow access to additional Client API endpoints (ListEntries, ListRelease).") + { + IsRequired = true + }; + + public static readonly Option ActionOption = new( + new[] + { + "-a", + "--action" + }, + "Determines the action (ListEntries, ListReleases, Write) for which the bucket permission will be updated.") + { + IsRequired = true + }; + + public static readonly Option PermissionOption = new( + new[] + { + "-m", + "--permission" + }, + "Specifies whether to (Allow, Deny) the defined action.") + { + IsRequired = true + }; + + public static readonly Option BucketDescriptionOption = new( + new[] + { + "-d", + "--description" + }, + "Description of the bucket."); + + public static readonly Option BucketPrivateOption = new( + new[] + { + "-i", + "--private" + }, + "Privacy setting of the bucket. Note that this setting cannot be changed once the bucket is created (default false)."); + + public static readonly Option SortByBucketOption = new( + new[] + { + "-s", + "--sort-by" + }, + "Sort by values (name, created)."); + + [InputBinding(nameof(PermissionOption))] + public CcdUpdatePermissionByBucketRequest.PermissionEnum Permission { get; set; } + + + [InputBinding(nameof(ActionOption))] + public CcdUpdatePermissionByBucketRequest.ActionEnum Action { get; set; } + + + [InputBinding(nameof(RoleOption))] + public CcdUpdatePermissionByBucketRequest.RoleEnum Role { get; set; } + + [InputBinding(nameof(BucketDescriptionOption))] + public string? BucketDescription { get; set; } + + [InputBinding(nameof(BucketPrivateOption))] + public bool? BucketPrivate { get; set; } + + [InputBinding(nameof(SortByBucketOption))] + public string? SortByBucket { get; set; } + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/BadgeResult.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/BadgeResult.cs new file mode 100644 index 0000000..7da1619 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/BadgeResult.cs @@ -0,0 +1,38 @@ +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +public class BadgeResult +{ + public BadgeResult( + CcdGetBucket200ResponseLastReleaseBadgesInner? response + ) + { + if (response == null) + throw new CliException( + "A server error occurred while retrieving the badge result. Please try again later.", + ExitCode.HandledError); + + Name = response.Name; + ReleaseId = response.Releaseid; + ReleaseNum = response.Releasenum; + Created = response.Created; + } + + public string? Name { get; set; } + public Guid ReleaseId { get; set; } + public long ReleaseNum { get; set; } + public DateTime Created { get; set; } + + public override string ToString() + { + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .DisableAliases() + .Build(); + return serializer.Serialize(this); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/BucketResult.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/BucketResult.cs new file mode 100644 index 0000000..7578f0f --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/BucketResult.cs @@ -0,0 +1,65 @@ +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +public class BucketResult +{ + public BucketResult( + CcdGetBucket200Response? response + ) + { + if (response == null) + throw new CliException( + "A server error occurred while retrieving the bucket result. Please try again later.", + ExitCode.HandledError); + + Id = response.Id; + Name = response.Name; + Created = response.Created; + Description = response.Description; + EnvironmentName = response.EnvironmentName; + EnvironmentId = response.EnvironmentId; + Projectguid = response.Projectguid; + Private = response.Private; + Permissions = response.Permissions; + LastRelease = response.LastRelease; + Changes = response.Changes; + Attributes = response.Attributes; + } + + public Guid Id { get; set; } + + public string? Name { get; set; } + + public string? Description { get; set; } + + public bool Private { get; set; } + + public Guid EnvironmentId { get; set; } + + public string? EnvironmentName { get; set; } + + public Guid Projectguid { get; set; } + + public CcdGetBucket200ResponseAttributes? Attributes { get; set; } + + public CcdGetBucket200ResponseChanges? Changes { get; set; } + + public DateTime Created { get; set; } + + public CcdGetBucket200ResponseLastRelease? LastRelease { get; set; } + + public CcdGetBucket200ResponsePermissions? Permissions { get; set; } + + public override string ToString() + { + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .DisableAliases() + .Build(); + return serializer.Serialize(this); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/EntryResult.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/EntryResult.cs new file mode 100644 index 0000000..607580d --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/EntryResult.cs @@ -0,0 +1,67 @@ +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +public class EntryResult +{ + public EntryResult(CcdCreateOrUpdateEntryBatch200ResponseInner? response) + { + if (response == null) + throw new CliException( + "A server error occurred while retrieving the entry result. Please try again later.", + ExitCode.HandledError); + + Entryid = response.Entryid; + CurrentVersionid = response.CurrentVersionid; + ContentHash = response.ContentHash; + Complete = response.Complete; + Labels = response.Labels; + Link = response.Link; + ContentSize = response.ContentSize; + ContentType = response.ContentType; + ContentHash = response.ContentHash; + ContentLink = response.ContentLink; + LastModified = response.LastModified; + Path = response.Path; + + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (response.Metadata != null) + Metadata = response.Metadata.ToString(); + } + + public Guid Entryid { get; set; } + + public string? Path { get; set; } + + public Guid CurrentVersionid { get; set; } + + public bool Complete { get; set; } + + public string ContentType { get; set; } + + public long ContentSize { get; set; } + + public string ContentHash { get; set; } + + public string? ContentLink { get; set; } + + public List Labels { get; set; } + + public DateTime LastModified { get; set; } + + public string Link { get; set; } + + public string? Metadata { get; set; } + + public override string ToString() + { + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .DisableAliases() + .Build(); + return serializer.Serialize(this); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListBadgeResult.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListBadgeResult.cs new file mode 100644 index 0000000..00f83d0 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListBadgeResult.cs @@ -0,0 +1,27 @@ +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +public class ListBadgeResult +{ + public ListBadgeResult(string releaseId, long releaseNum, string name) + { + ReleaseId = releaseId; + ReleaseNum = releaseNum; + Name = name; + } + + public string Name { get; set; } + public string ReleaseId { get; } + public long ReleaseNum { get; set; } + + public override string ToString() + { + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .DisableAliases() + .Build(); + return serializer.Serialize(this); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListBucketResult.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListBucketResult.cs new file mode 100644 index 0000000..dbdd6f6 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListBucketResult.cs @@ -0,0 +1,25 @@ +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +public class ListBucketResult +{ + public ListBucketResult(string id, string name) + { + Id = id; + Name = name; + } + + public string Id { get; set; } + public string Name { get; set; } + + public override string ToString() + { + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .DisableAliases() + .Build(); + return serializer.Serialize(this); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListEntryResult.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListEntryResult.cs new file mode 100644 index 0000000..bff299c --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListEntryResult.cs @@ -0,0 +1,25 @@ +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +public class ListEntryResult +{ + public ListEntryResult(string id, string name) + { + Id = id; + Name = name; + } + + public string Id { get; set; } + public string Name { get; set; } + + public override string ToString() + { + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .DisableAliases() + .Build(); + return serializer.Serialize(this); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListReleaseResult.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListReleaseResult.cs new file mode 100644 index 0000000..52936b1 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ListReleaseResult.cs @@ -0,0 +1,27 @@ +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +public class ListReleaseResult +{ + public ListReleaseResult( + string releaseId, + long releaseNum) + { + ReleaseId = releaseId; + ReleaseNum = releaseNum; + } + + public string ReleaseId { get; set; } + public long ReleaseNum { get; set; } + + public override string ToString() + { + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .DisableAliases() + .Build(); + return serializer.Serialize(this); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/LongOperationSummary.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/LongOperationSummary.cs new file mode 100644 index 0000000..dea25b8 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/LongOperationSummary.cs @@ -0,0 +1,62 @@ +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +public class LongOperationSummary : IOperationSummary +{ + + public List EntriesToAdd { get; private set; } + public List EntriesToUpdate { get; private set; } + public List EntriesToDelete { get; private set; } + public List EntriesToSkip { get; private set; } + public ReleaseResult? Release { get; private set; } + public BadgeResult? Badge { get; private set; } + public string Operations { get; private set; } + + public double SynchronizationTimeInSeconds { get; private set; } + public double TotalUploadedDataSizeInMb { get; private set; } + public double AverageUploadSpeedInMbps { get; private set; } + public int TotalNumberOfFilesUploaded { get; private set; } + public bool OperationCompletedSuccessfully { get; private set; } + + public LongOperationSummary( + SyncResult syncResult, + bool operationCompletedSuccessfully, + double synchronizationTimeInSeconds, + double totalUploadedDataSizeInMb, + double averageUploadSpeedInMbps, + int totalNumberOfFilesUploaded, + CcdGetBucket200ResponseLastRelease? release, + CcdGetBucket200ResponseLastReleaseBadgesInner? badge) + { + + EntriesToAdd = syncResult.EntriesToAdd.Select(entry => entry.Path).ToList(); + EntriesToUpdate = syncResult.EntriesToUpdate.Select(entry => entry.Path).ToList(); + EntriesToDelete = syncResult.EntriesToDelete.Select(entry => entry.Path).ToList(); + EntriesToSkip = syncResult.EntriesToSkip.Select(entry => entry.Path).ToList(); + + Operations = syncResult.GetSummary(); + OperationCompletedSuccessfully = operationCompletedSuccessfully; + SynchronizationTimeInSeconds = Math.Round(synchronizationTimeInSeconds, 4); + TotalUploadedDataSizeInMb = Math.Round(totalUploadedDataSizeInMb, 4); + AverageUploadSpeedInMbps = Math.Round(averageUploadSpeedInMbps, 4); + TotalNumberOfFilesUploaded = totalNumberOfFilesUploaded; + + if (release != null) + Release = new ReleaseResult(release); + if (badge != null) + Badge = new BadgeResult(badge); + } + + public override string ToString() + { + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .DisableAliases() + .Build(); + return serializer.Serialize(this); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/PermissionResult.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/PermissionResult.cs new file mode 100644 index 0000000..9a5748a --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/PermissionResult.cs @@ -0,0 +1,36 @@ +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +public class PermissionResult +{ + public PermissionResult( + CcdGetAllByBucket200ResponseInner? response + ) + { + if (response == null) + throw new CliException( + "A server error occurred while retrieving the permission result. Please try again later.", + ExitCode.HandledError); + + Action = response.Action; + Permission = response.Permission; + Role = response.Role; + } + + public string Action { get; set; } + public string Permission { get; set; } + public string Role { get; set; } + + public override string ToString() + { + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .DisableAliases() + .Build(); + return serializer.Serialize(this); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/PromoteResult.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/PromoteResult.cs new file mode 100644 index 0000000..c6a58f4 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/PromoteResult.cs @@ -0,0 +1,30 @@ +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +public class PromoteResult : CcdPromoteBucketAsync200Response +{ + public PromoteResult( + CcdPromoteBucketAsync200Response? response + ) + { + if (response == null) + throw new CliException( + "A server error occurred while retrieving the promote result. Please try again later.", + ExitCode.HandledError); + + PromotionId = response.PromotionId; + } + + public override string ToString() + { + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .DisableAliases() + .Build(); + return serializer.Serialize(this); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/PromotionResult.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/PromotionResult.cs new file mode 100644 index 0000000..4398010 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/PromotionResult.cs @@ -0,0 +1,44 @@ +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +public class PromotionResult : CcdGetPromotions200ResponseInner +{ + public PromotionResult( + CcdGetPromotions200ResponseInner? response + ) + { + if (response == null) + throw new CliException( + "A server error occurred while retrieving the release result. Please try again later.", + ExitCode.HandledError); + + PromotionId = response.PromotionId; + PromotionStatus = response.PromotionStatus; + FromBucketName = response.FromBucketName; + FromBucketId = response.FromBucketId; + FromEnvironmentId = response.FromEnvironmentId; + FromEnvironmentName = response.FromEnvironmentName; + FromReleaseId = response.FromReleaseId; + FromReleaseNumber = response.FromReleaseNumber; + Error = response.Error; + ToBucketId = response.ToBucketId; + ToEnvironmentId = response.ToEnvironmentId; + ToReleaseId = response.ToReleaseId; + ToBucketName = response.ToBucketName; + ToEnvironmentName = response.ToEnvironmentName; + + } + + public override string ToString() + { + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .DisableAliases() + .Build(); + return serializer.Serialize(this); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ReleaseResult.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ReleaseResult.cs new file mode 100644 index 0000000..0c59b96 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ReleaseResult.cs @@ -0,0 +1,68 @@ +using Newtonsoft.Json; +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +public class ReleaseResult +{ + public ReleaseResult( + CcdGetBucket200ResponseLastRelease? response + ) + { + if (response == null) + throw new CliException( + "A server error occurred while retrieving the release result. Please try again later.", + ExitCode.HandledError); + + Notes = response.Notes; + Badges = response.Badges; + ReleaseId = response.Releaseid; + ReleaseNum = response.Releasenum; + PromotedFromBucket = response.PromotedFromBucket; + PromotedFromRelease = response.PromotedFromRelease; + Created = response.Created; + Changes = response.Changes; + ContentHash = response.ContentHash; + ContentSize = response.ContentSize; + EntriesLink = response.EntriesLink; + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (response.Metadata != null) + Metadata = JsonConvert.SerializeObject(response.Metadata, Formatting.Indented); + } + + public Guid ReleaseId { get; set; } + + public long ReleaseNum { get; set; } + + public long ContentSize { get; set; } + + public string? ContentHash { get; set; } + + public CcdGetBucket200ResponseChanges? Changes { get; set; } + + public List? Badges { get; set; } + + public DateTime Created { get; set; } + + public string? EntriesLink { get; set; } + + public string? Metadata { get; set; } + + public string? Notes { get; set; } + + public Guid PromotedFromBucket { get; set; } + + public Guid PromotedFromRelease { get; set; } + + public override string ToString() + { + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .DisableAliases() + .Build(); + return serializer.Serialize(this); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/SharedRateLimitStatus.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/SharedRateLimitStatus.cs new file mode 100644 index 0000000..5753ba6 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/SharedRateLimitStatus.cs @@ -0,0 +1,13 @@ +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +class SharedRateLimitStatus +{ + public bool IsRateLimited { get; set; } + public TimeSpan ResetTime { get; set; } = TimeSpan.Zero; + + public void UpdateRateLimit(bool rateLimited, TimeSpan resetTime) + { + IsRateLimited = rateLimited; + ResetTime = resetTime; + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ShortOperationSummary.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ShortOperationSummary.cs new file mode 100644 index 0000000..f395f7b --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/ShortOperationSummary.cs @@ -0,0 +1,44 @@ +using Unity.Services.Cli.CloudContentDelivery.Service; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +public class ShortOperationSummary : IOperationSummary +{ + + public ReleaseResult? Release { get; private set; } + public BadgeResult? Badge { get; private set; } + public string Operations { get; private set; } + public double SynchronizationTimeInSeconds { get; private set; } + + public bool OperationCompletedSuccessfully { get; private set; } + + public ShortOperationSummary( + SyncResult syncResult, + bool operationCompletedSuccessfully, + double synchronizationTimeInSeconds, + CcdGetBucket200ResponseLastRelease? release, + CcdGetBucket200ResponseLastReleaseBadgesInner? badge) + { + + Operations = syncResult.GetSummary(); + OperationCompletedSuccessfully = operationCompletedSuccessfully; + SynchronizationTimeInSeconds = Math.Round(synchronizationTimeInSeconds, 4); + + if (release != null) + Release = new ReleaseResult(release); + if (badge != null) + Badge = new BadgeResult(badge); + } + + public override string ToString() + { + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .DisableAliases() + .Build(); + return serializer.Serialize(this); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/SyncEntry.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/SyncEntry.cs new file mode 100644 index 0000000..0ad4e91 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/SyncEntry.cs @@ -0,0 +1,42 @@ +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +public class SyncEntry +{ + public SyncEntry( + string path, + string environmentId = "", + string bucketId = "", + string projectId = "", + long? contentSize = null, + string contentType = "", + string contentHash = "", + string? entryId = "", + string? versionId = "", + List? labels = null, + object? metadata = null) + { + Path = path ?? throw new ArgumentNullException(nameof(path)); + EnvironmentId = environmentId; + BucketId = bucketId; + ProjectId = projectId; + ContentSize = contentSize!; + ContentType = contentType; + ContentHash = contentHash; + Labels = labels; + Metadata = metadata; + EntryId = entryId ?? ""; + VersionId = versionId ?? ""; + } + + public string Path { get; } + public long? ContentSize { get; } + public string ContentType { get; } + public string ContentHash { get; } + public string EnvironmentId { get; } + public string BucketId { get; } + public string ProjectId { get; } + public List? Labels { get; } + public object? Metadata { get; } + public string EntryId { get; set; } + public string VersionId { get; set; } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/SyncResult.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/SyncResult.cs new file mode 100644 index 0000000..cabcce4 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Model/SyncResult.cs @@ -0,0 +1,31 @@ +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Unity.Services.Cli.CloudContentDelivery.Model; + +public class SyncResult +{ + + public List EntriesToAdd { get; set; } = new(); + public List EntriesToUpdate { get; set; } = new(); + public List EntriesToDelete { get; set; } = new(); + public List EntriesToSkip { get; set; } = new(); + + public string GetSummary() + { + var totalCount = EntriesToAdd.Count + EntriesToUpdate.Count + + EntriesToDelete.Count + EntriesToSkip.Count; + return $"Total Operation Count: {totalCount}, Entries To Add: {EntriesToAdd.Count}, Entries To Update: {EntriesToUpdate.Count}, Entries To Delete: {EntriesToDelete.Count}, Entries To Skip: {EntriesToSkip.Count}"; + } + + public override string ToString() + { + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .DisableAliases() + .Build(); + return serializer.Serialize(this); + } + + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/BadgeClient.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/BadgeClient.cs new file mode 100644 index 0000000..1be034f --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/BadgeClient.cs @@ -0,0 +1,103 @@ +using Unity.Services.Cli.ServiceAccountAuthentication; +using Unity.Services.Cli.ServiceAccountAuthentication.Token; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Api; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +public class BadgeClient : IBadgeClient +{ + readonly IServiceAccountAuthenticationService m_AuthenticationService; + readonly IBadgesApi m_BadgesApi; + readonly IContentDeliveryValidator m_ContentDeliveryValidator; + + public BadgeClient( + IServiceAccountAuthenticationService authenticationService, + IBadgesApi badgesApi, + IContentDeliveryValidator contentDeliveryValidator) + { + m_AuthenticationService = authenticationService; + m_ContentDeliveryValidator = contentDeliveryValidator; + m_BadgesApi = badgesApi; + } + + public async Task>> ListBadgeAsync( + string projectId, + string environmentId, + string bucketId, + int? page, + int? perPage, + string filterName, + string releaseNum, + string sortBy, + string sortOrder, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + + return await m_BadgesApi.ListBadgesEnvWithHttpInfoAsync( + environmentId, + bucketId, + projectId, + page, + perPage, + filterName, + releaseNum, + sortBy, + sortOrder, + 0, + cancellationToken); + } + + public async Task CreateBadgeAsync( + string projectId, + string environmentId, + string bucketId, + string badgeName, + long releaseNum, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + var releaseNumber = releaseNum; + + var ccdUpdateBadgeRequest = + new CcdUpdateBadgeRequest(badgeName, default, releaseNumber); + return await m_BadgesApi.UpdateBadgeEnvAsync( + environmentId, + bucketId, + projectId, + ccdUpdateBadgeRequest, + 0, + cancellationToken); + } + + public async Task DeleteBadgeAsync( + string projectId, + string environmentId, + string bucketId, + string badgeName, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + m_ContentDeliveryValidator.ValidateBucketId(bucketId); + + await m_BadgesApi.DeleteBadgeEnvAsync( + environmentId, + bucketId, + badgeName, + projectId, + 0, + cancellationToken); + return "Badge deleted."; + } + + public async Task AuthorizeServiceAsync(CancellationToken cancellationToken = default) + { + var token = await m_AuthenticationService.GetAccessTokenAsync(cancellationToken); + m_BadgesApi.Configuration.DefaultHeaders.SetAccessTokenHeader(token); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/BucketClient.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/BucketClient.cs new file mode 100644 index 0000000..f8ad996 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/BucketClient.cs @@ -0,0 +1,296 @@ +using System.Net; +using Newtonsoft.Json; +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Cli.ServiceAccountAuthentication; +using Unity.Services.Cli.ServiceAccountAuthentication.Token; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Api; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +public class BucketClient : IBucketClient +{ + readonly IServiceAccountAuthenticationService m_AuthenticationService; + readonly IBucketsApi m_BucketsApi; + readonly IContentDeliveryValidator m_ContentDeliveryValidator; + readonly IPermissionsApi m_PermissionsApi; + + public BucketClient( + IServiceAccountAuthenticationService authenticationService, + IBucketsApi bucketsApi, + IPermissionsApi permissionsApi, + IContentDeliveryValidator contentDeliveryValidator) + { + m_AuthenticationService = authenticationService; + m_ContentDeliveryValidator = contentDeliveryValidator; + m_BucketsApi = bucketsApi; + m_PermissionsApi = permissionsApi; + } + + public async + Task>> ListBucketAsync( + string projectId, + string environmentId, + int? page, + int? perPage, + string filterName, + string description, + string sortBy, + string sortOrder, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + + return await m_BucketsApi.ListBucketsByProjectEnvWithHttpInfoAsync( + environmentId, + projectId, + page, + perPage, + filterName, + description, + sortBy, + sortOrder, + 0, + cancellationToken); + } + + + public async Task GetBucketAsync( + string projectId, + string environmentId, + string bucketName, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + var bucketId = await GetBucketIdByName( + projectId, + environmentId, + bucketName, + cancellationToken); + + var response = await m_BucketsApi.GetBucketEnvAsync( + environmentId, + bucketId, + projectId, + 0, + cancellationToken); + return response; + } + + public async Task CreateBucketAsync( + string projectId, + string environmentId, + string name, + string description, + bool isPrivate, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + + var + ccdBucket = + new CcdCreateBucketByProjectRequest( + name: name, + projectguid: Guid.Parse(projectId), + description: description, + _private: isPrivate + ); + var response = await m_BucketsApi.CreateBucketByProjectEnvAsync( + environmentId, + projectId, + ccdBucket, + 0, + cancellationToken); + return response; + } + + public async Task DeleteBucketAsync( + string projectId, + string environmentId, + string bucketName, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + var bucketId = await GetBucketIdByName( + projectId, + environmentId, + bucketName, + cancellationToken); + + await m_BucketsApi.DeleteBucketEnvAsync( + environmentId, + bucketId, + projectId, + 0, + cancellationToken); + + return "Bucket deleted."; + } + + public async Task UpdatePermissionBucketAsync( + string projectId, + string environmentId, + string bucketName, + CcdUpdatePermissionByBucketRequest.ActionEnum action, + CcdUpdatePermissionByBucketRequest.PermissionEnum permission, + CcdUpdatePermissionByBucketRequest.RoleEnum role, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + var bucketId = await GetBucketIdByName( + projectId, + environmentId, + bucketName, + cancellationToken); + + var ccdUpdatePermissionByBucketRequest = new CcdUpdatePermissionByBucketRequest( + action, + permission, + role); + var permissionList = await m_PermissionsApi.GetAllByBucketEnvAsync( + environmentId, + bucketId, + projectId, + 0, + cancellationToken); + + // Problem: Due to permissions list result not being typed, (we receive a string instead of an enum) + // we need to compare the strings as they are serialized against what we receive + var actionStr = JsonConvert.SerializeObject(action).Replace("\"",""); + var roleStr = JsonConvert.SerializeObject(role).Replace("\"",""); + + var writePermissions = permissionList + .Find(x => + x.Action == actionStr + && x.Role == roleStr); + + if (writePermissions != null) + return await m_PermissionsApi.UpdatePermissionByBucketEnvAsync( + environmentId, + bucketId, + projectId, + ccdUpdatePermissionByBucketRequest, + 0, + cancellationToken); + return await m_PermissionsApi.CreatePermissionByBucketEnvAsync( + environmentId, + bucketId, + projectId, + ccdUpdatePermissionByBucketRequest, + 0, + cancellationToken); + } + + public async Task PromoteBucketEnvAsync( + string projectId, + string environmentId, + string bucketId, + string fromRelease, + string notes, + string toBucket, + string toEnvironment, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + + var promoteBucketRequest = new CcdPromoteBucketRequest( + new Guid(fromRelease), + notes, + new Guid(toBucket), + new Guid(toEnvironment)); + + return await m_BucketsApi.PromoteBucketAsyncEnvAsync( + environmentId, + bucketId, + projectId, + promoteBucketRequest, + 0, + cancellationToken); + } + + public async Task GetPromotionAsync( + string projectId, + string environmentId, + string bucketId, + string promotionId, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + + return await m_BucketsApi.GetPromotionEnvAsync( + environmentId, + bucketId, + promotionId, + projectId, + 0, + cancellationToken); + } + + public async Task GetBucketIdByName( + string projectId, + string environmentId, + string bucketName, + CancellationToken cancellationToken) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + + var page = 1; + const int perPage = 100; + + while (true) + { + try + { + var result = await m_BucketsApi.ListBucketsByProjectEnvWithHttpInfoAsync( + environmentId, + projectId, + page, + perPage, + bucketName, + null, + null, + null, + 0, + cancellationToken); + + var exactMatch = result.Data.FirstOrDefault( + bucket => string.Equals(bucket.Name, bucketName, StringComparison.OrdinalIgnoreCase)); + if (exactMatch != null) return exactMatch.Id.ToString(); + + if (result.StatusCode != HttpStatusCode.OK || !result.Data.Any()) break; + + } + catch (ApiException ex) when (ex.ErrorCode == (int)HttpStatusCode.RequestedRangeNotSatisfiable) + { + break; + } + catch (Exception ex) + { + throw new CliException( + $"A server error occured while fetching the bucket: {ex.Message}, please try again.", + ExitCode.HandledError); + } + + page++; + } + + throw new CliException($"No bucket exists with the name: {bucketName}", ExitCode.HandledError); + + } + + internal async Task AuthorizeServiceAsync(CancellationToken cancellationToken = default) + { + var token = await m_AuthenticationService.GetAccessTokenAsync(cancellationToken); + m_BucketsApi.Configuration.DefaultHeaders.SetAccessTokenHeader(token); + m_PermissionsApi.Configuration.DefaultHeaders.SetAccessTokenHeader(token); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ClientWrapper.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ClientWrapper.cs new file mode 100644 index 0000000..0e61e0e --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ClientWrapper.cs @@ -0,0 +1,22 @@ +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +public class ClientWrapper : IClientWrapper +{ + public IReleaseClient? ReleaseClient { get; private set; } + public IBadgeClient? BadgeClient { get; private set; } + public IBucketClient? BucketClient { get; private set; } + public IEntryClient? EntryClient { get; private set; } + + public ClientWrapper( + IReleaseClient? releaseClient, + IBadgeClient? badgeClient, + IBucketClient? bucketClient, + IEntryClient? entryClient) + { + ReleaseClient = releaseClient; + BadgeClient = badgeClient; + BucketClient = bucketClient; + EntryClient = entryClient; + } + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ContentDeliveryValidator.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ContentDeliveryValidator.cs new file mode 100644 index 0000000..edd4979 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ContentDeliveryValidator.cs @@ -0,0 +1,40 @@ +using Unity.Services.Cli.Common.Models; +using Unity.Services.Cli.Common.Validator; + +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +public class ContentDeliveryValidator : IContentDeliveryValidator +{ + readonly IConfigurationValidator m_ConfigValidator; + + public ContentDeliveryValidator(IConfigurationValidator configValidator) + { + m_ConfigValidator = configValidator ?? throw new ArgumentNullException(nameof(configValidator)); + } + + public void ValidateProjectIdAndEnvironmentId(string projectId, string environmentId) + { + ValidateConfig(Keys.ConfigKeys.ProjectId, projectId); + ValidateConfig(Keys.ConfigKeys.EnvironmentId, environmentId); + } + + public void ValidateBucketId(string bucketId) + { + ValidateConfig(Keys.ConfigKeys.BucketName, bucketId); + } + + public void ValidateEntryId(string entryId) + { + if (!Guid.TryParse(entryId, out _)) throw new ArgumentException("Invalid entryId. It must be a valid GUID."); + } + + public void ValidatePath(string? path) + { + if (string.IsNullOrEmpty(path)) throw new ArgumentException("Invalid Path. The path must not be empty."); + } + + void ValidateConfig(string key, string value) + { + m_ConfigValidator.ThrowExceptionIfConfigInvalid(key, value); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/EntryClient.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/EntryClient.cs new file mode 100644 index 0000000..6990a67 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/EntryClient.cs @@ -0,0 +1,270 @@ +using System.IO.Abstractions; +using Unity.Services.Cli.CloudContentDelivery.Utils; +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Cli.ServiceAccountAuthentication; +using Unity.Services.Cli.ServiceAccountAuthentication.Token; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Api; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +class EntryClient : IEntryClient +{ + readonly IServiceAccountAuthenticationService m_AuthenticationService; + readonly IContentDeliveryValidator m_ContentDeliveryValidator; + readonly IEntriesApi m_EntriesApi; + readonly IContentApi m_ContentApi; + readonly IFileSystem m_FileSystem; + readonly IUploadContentClient m_UploadContentClient; + + public EntryClient( + IServiceAccountAuthenticationService authenticationService, + IContentDeliveryValidator contentDeliveryValidator, + IEntriesApi entriesApi, + IContentApi contentApi, + IUploadContentClient uploadContentClient, + IFileSystem fileSystem) + { + m_AuthenticationService = authenticationService; + m_ContentDeliveryValidator = contentDeliveryValidator; + m_EntriesApi = entriesApi; + m_ContentApi = contentApi; + m_UploadContentClient = uploadContentClient; + m_FileSystem = fileSystem; + } + + public async Task UpdateEntryAsync( + string projectId, + string environmentId, + string bucketId, + string entryPath, + string versionId, + List? labels, + string? metadata, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + + var ccdUpdateEntryRequest = new CcdUpdateEntryRequest(); + + if (labels != null) + ccdUpdateEntryRequest.Labels = labels; + if (metadata != null) + ccdUpdateEntryRequest.Metadata = CcdUtils.ParseMetadata(metadata)!; + + var responseEntry = await m_EntriesApi.GetEntryByPathEnvAsync( + environmentId, + bucketId, + entryPath, + projectId, + versionId, + 0, + cancellationToken); + var entryId = responseEntry.Entryid.ToString(); + + var response = await m_EntriesApi.UpdateEntryEnvAsync( + environmentId, + bucketId, + entryId, + projectId, + ccdUpdateEntryRequest, + 0, + cancellationToken); + return response; + } + + public async Task DownloadEntryAsync( + string projectId, + string environmentId, + string bucketId, + string path, + string versionId, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + m_ContentDeliveryValidator.ValidateBucketId(bucketId); + + var responseEntry = await m_EntriesApi.GetEntryByPathEnvAsync( + environmentId, + bucketId, + path, + projectId, + versionId, + 0, + cancellationToken); + var entryId = responseEntry.Entryid.ToString(); + + var response = await m_ContentApi.GetContentEnvAsync( + environmentId, + bucketId, + entryId, + projectId, + versionId, + 0, + cancellationToken); + return response; + } + + public async Task GetEntryAsync( + string projectId, + string environmentId, + string bucketId, + string entryPath, + string versionId, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + m_ContentDeliveryValidator.ValidateBucketId(bucketId); + + return await m_EntriesApi.GetEntryByPathEnvAsync( + environmentId, + bucketId, + entryPath, + projectId, + versionId, + 0, + cancellationToken); + + } + + public async Task DeleteEntryAsync( + string projectId, + string environmentId, + string bucketId, + string entryPath, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + m_ContentDeliveryValidator.ValidateBucketId(bucketId); + + var response = await m_EntriesApi.GetEntryByPathEnvAsync( + environmentId, + bucketId, + entryPath, + projectId, + null, + 0, + cancellationToken); + + await m_EntriesApi.DeleteEntryEnvAsync( + environmentId, + bucketId, + response.Entryid.ToString(), + projectId, + 0, + cancellationToken); + return "Entry Deleted."; + } + + public async Task CopyEntryAsync( + string projectId, + string environmentId, + string bucketId, + string localPath, + string remotePath, + List labels, + string? metadata, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + m_ContentDeliveryValidator.ValidateBucketId(bucketId); + m_ContentDeliveryValidator.ValidatePath(localPath); + m_ContentDeliveryValidator.ValidatePath(remotePath); + + await using var filestream = m_FileSystem.File.OpenRead(localPath); + var contentSize = m_UploadContentClient.GetContentSize(filestream); + var contentType = m_UploadContentClient.GetContentType(localPath); + var contentHash = m_UploadContentClient.GetContentHash(filestream); + var entryMetadata = metadata != null ? CcdUtils.ParseMetadata(metadata)! : string.Empty; + + // CREATE OR UPDATE ENTRY + var ccdCreateOrUpdateEntryByPathRequest = + new CcdCreateOrUpdateEntryByPathRequest( + contentHash, + contentSize, + contentType, + labels, + entryMetadata, + true); + var entry = await m_EntriesApi.CreateOrUpdateEntryByPathEnvAsync( + environmentId, + bucketId, + remotePath, + projectId, + ccdCreateOrUpdateEntryByPathRequest, + true, + 0, + cancellationToken); + + // UPLOAD ENTRY CONTENT + var response = await m_UploadContentClient.UploadContentToCcd( + entry.SignedUrl, + filestream, + cancellationToken); + + if (!response.IsSuccessStatusCode) + throw new CliException( + $"There was an error while uploading content to ccd: {response.ReasonPhrase}", + ExitCode.HandledError); + + // GET CREATED ENTRY + var response3 = await m_EntriesApi.GetEntryByPathEnvAsync( + environmentId, + bucketId, + remotePath, + projectId, + entry.CurrentVersionid.ToString(), + 0, + cancellationToken); + return response3; + } + + public async Task>> ListEntryAsync( + string projectId, + string environmentId, + string bucketId, + int? page, + string? startingAfter, + int? perPage, + string? path, + string? label, + string? contentType, + bool? complete, + string? sortBy, + string? sortOrder, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + m_ContentDeliveryValidator.ValidateBucketId(bucketId); + + return await m_EntriesApi.GetEntriesEnvWithHttpInfoAsync( + environmentId, + bucketId, + projectId, + page, + string.IsNullOrEmpty(startingAfter) ? null : new Guid(startingAfter), + perPage, + path, + label, + contentType, + complete, + sortBy, + sortOrder, + 0, + cancellationToken); + } + + public async Task AuthorizeServiceAsync(CancellationToken cancellationToken = default) + { + var token = await m_AuthenticationService.GetAccessTokenAsync(cancellationToken); + m_EntriesApi.Configuration.DefaultHeaders.SetAccessTokenHeader(token); + m_ContentApi.Configuration.DefaultHeaders.SetAccessTokenHeader(token); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IBadgeClient.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IBadgeClient.cs new file mode 100644 index 0000000..725b202 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IBadgeClient.cs @@ -0,0 +1,36 @@ +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +public interface IBadgeClient +{ + Task>> ListBadgeAsync( + string projectId, + string environmentId, + string bucketId, + int? page, + int? perPage, + string filterName, + string releaseNum, + string sortBy, + string sortOrder, + CancellationToken cancellationToken = default); + + Task CreateBadgeAsync( + string projectId, + string environmentId, + string bucketId, + string badgeName, + long releaseNum, + CancellationToken cancellationToken = default); + + Task DeleteBadgeAsync( + string projectId, + string environmentId, + string bucketId, + string badgeName, + CancellationToken cancellationToken = default); + + Task AuthorizeServiceAsync(CancellationToken cancellationToken = default); +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IBucketClient.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IBucketClient.cs new file mode 100644 index 0000000..31ea500 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IBucketClient.cs @@ -0,0 +1,70 @@ +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +public interface IBucketClient +{ + Task>> ListBucketAsync( + string projectId, + string environmentId, + int? page, + int? perPage, + string filterName, + string description, + string sortBy, + string sortOrder, + CancellationToken cancellationToken = default); + + Task GetBucketAsync( + string projectId, + string environmentId, + string bucketId, + CancellationToken cancellationToken = default); + + Task CreateBucketAsync( + string projectId, + string environmentId, + string name, + string description, + bool isPrivate, + CancellationToken cancellationToken = default); + + Task DeleteBucketAsync( + string projectId, + string environmentId, + string bucketId, + CancellationToken cancellationToken = default); + + Task UpdatePermissionBucketAsync( + string projectId, + string environmentId, + string bucketId, + CcdUpdatePermissionByBucketRequest.ActionEnum action, + CcdUpdatePermissionByBucketRequest.PermissionEnum permission, + CcdUpdatePermissionByBucketRequest.RoleEnum role, + CancellationToken cancellationToken = default); + + Task PromoteBucketEnvAsync( + string projectId, + string environmentId, + string bucketId, + string fromRelease, + string notes, + string toBucket, + string toEnvironment, + CancellationToken cancellationToken = default); + + Task GetPromotionAsync( + string projectId, + string environmentId, + string bucketId, + string promotionId, + CancellationToken cancellationToken = default); + + Task GetBucketIdByName( + string projectId, + string environmentId, + string bucketName, + CancellationToken cancellationToken); +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IClientWrapper.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IClientWrapper.cs new file mode 100644 index 0000000..bcda78b --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IClientWrapper.cs @@ -0,0 +1,9 @@ +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +interface IClientWrapper +{ + IReleaseClient? ReleaseClient { get; } + IBadgeClient? BadgeClient { get; } + IBucketClient? BucketClient { get; } + IEntryClient? EntryClient { get; } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IContentDeliveryValidator.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IContentDeliveryValidator.cs new file mode 100644 index 0000000..ba794e9 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IContentDeliveryValidator.cs @@ -0,0 +1,9 @@ +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +public interface IContentDeliveryValidator +{ + void ValidateProjectIdAndEnvironmentId(string projectId, string environmentId); + void ValidateBucketId(string bucketId); + void ValidateEntryId(string entryId); + void ValidatePath(string path); +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IEntryClient.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IEntryClient.cs new file mode 100644 index 0000000..cb4d85e --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IEntryClient.cs @@ -0,0 +1,67 @@ +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +public interface IEntryClient +{ + Task UpdateEntryAsync( + string projectId, + string environmentId, + string bucketId, + string entryPath, + string versionId, + List? labels, + string? metadata, + CancellationToken cancellationToken = default); + + Task GetEntryAsync( + string projectId, + string environmentId, + string bucketId, + string entryPath, + string versionId, + CancellationToken cancellationToken = default); + + public Task DownloadEntryAsync( + string projectId, + string environmentId, + string bucketId, + string path, + string versionId, + CancellationToken cancellationToken = default); + + Task DeleteEntryAsync( + string projectId, + string environmentId, + string bucketId, + string entryPath, + CancellationToken cancellationToken = default); + + Task CopyEntryAsync( + string projectId, + string environmentId, + string bucketId, + string localPath, + string remotePath, + List labels, + string? metadata, + CancellationToken cancellationToken = default); + + Task>> ListEntryAsync( + string projectId, + string environmentId, + string bucketId, + int? page, + string? startingAfter, + int? perPage, + string? path, + string? label, + string? contentType, + bool? complete, + string? sortBy, + string? sortOrder, + CancellationToken cancellationToken = default); + + Task AuthorizeServiceAsync(CancellationToken cancellationToken = default); +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IOperationSummary.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IOperationSummary.cs new file mode 100644 index 0000000..73cb338 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IOperationSummary.cs @@ -0,0 +1,15 @@ +using Unity.Services.Cli.CloudContentDelivery.Model; + +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +public interface IOperationSummary +{ + + ReleaseResult? Release { get; } + BadgeResult? Badge { get; } + + public string Operations { get; } + public double SynchronizationTimeInSeconds { get; } + + public bool OperationCompletedSuccessfully { get; } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IReleaseClient.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IReleaseClient.cs new file mode 100644 index 0000000..ea74898 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IReleaseClient.cs @@ -0,0 +1,60 @@ +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +public interface IReleaseClient +{ + Task CreateReleaseAsync( + string projectId, + string environmentId, + string bucketId, + CcdCreateReleaseRequest ccdRelease, + CancellationToken cancellationToken = default); + + Task UpdateReleaseAsync( + string projectId, + string environmentId, + string bucketId, + int releaseNum, + string notes, + CancellationToken cancellationToken = default); + + Task GetReleaseAsync( + string projectId, + string environmentId, + string bucketId, + int releaseNum, + CancellationToken cancellationToken = default); + + Task GetReleaseByBadgeNameAsync( + string projectId, + string environmentId, + string bucketId, + string badgeName, + CancellationToken cancellationToken = default); + + Task>> ListReleaseAsync( + string projectId, + string environmentId, + string bucketId, + int? page, + int? perPage, + string? releaseNum, + string? promotedFromBucket, + string? promotedFromRelease, + string? badges, + string? notes, + string? sortBy, + string? sortOrder, + CancellationToken cancellationToken = default); + + public Task GetReleaseIdByNumber( + string projectId, + string environmentId, + string bucketId, + int releaseNum, + CancellationToken cancellationToken); + + Task AuthorizeServiceAsync(CancellationToken cancellationToken = default); +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ISynchronizationService.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ISynchronizationService.cs new file mode 100644 index 0000000..a6ebe29 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ISynchronizationService.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +public interface ISynchronizationService +{ + Task CalculateSynchronization( + string projectId, + string environmentId, + string bucketId, + string localFolder, + string? exclusionPattern, + bool delete, + List? labels, + object? metadata, + CancellationToken cancellationToken); + + SyncResult CalculateDifference( + string projectId, + string environmentId, + string bucketId, + string localFolder, + bool deleteIfFileNotPresentInLocalFolder, + List remoteEntries, + HashSet localFiles, + List? labels, + object? metadata); + + Task> ProcessSynchronization( + ILogger logger, + bool verbose, + SyncResult syncResult, + string localFolder, + int retryCount, + int maxConcurrentRequests, + int retryDelayMilliseconds, + CancellationToken cancellationToken); + + HashSet GetFilesFromDir( + string directoryPath, + string? exclusionPattern); + + Task AuthorizeServiceAsync(CancellationToken cancellationToken = default); +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IUploadContentClient.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IUploadContentClient.cs new file mode 100644 index 0000000..3a9b9e2 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/IUploadContentClient.cs @@ -0,0 +1,16 @@ +using System.IO.Abstractions; + +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +public interface IUploadContentClient +{ + string GetContentType(string localPath); + + Task UploadContentToCcd( + string signedUrl, + FileSystemStream filestream, + CancellationToken cancellationToken = default); + + string GetContentHash(FileSystemStream filestream); + long GetContentSize(FileSystemStream filestream); +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ReleaseClient.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ReleaseClient.cs new file mode 100644 index 0000000..d51f120 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/ReleaseClient.cs @@ -0,0 +1,195 @@ +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Cli.ServiceAccountAuthentication; +using Unity.Services.Cli.ServiceAccountAuthentication.Token; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Api; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +class ReleaseClient : IReleaseClient +{ + readonly IServiceAccountAuthenticationService m_AuthenticationService; + readonly IContentDeliveryValidator m_ContentDeliveryValidator; + readonly IReleasesApi m_ReleasesApi; + + public ReleaseClient( + IServiceAccountAuthenticationService authenticationService, + IReleasesApi releasesApi, + IContentDeliveryValidator contentDeliveryValidator) + { + m_AuthenticationService = authenticationService; + m_ContentDeliveryValidator = contentDeliveryValidator; + m_ReleasesApi = releasesApi; + } + + public async Task CreateReleaseAsync( + string projectId, + string environmentId, + string bucketId, + CcdCreateReleaseRequest ccdRelease, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + + var response = await m_ReleasesApi.CreateReleaseEnvAsync( + environmentId, + bucketId, + projectId, + ccdRelease, + 0, + cancellationToken); + return response; + } + + public async Task UpdateReleaseAsync( + string projectId, + string environmentId, + string bucketId, + int releaseNum, + string notes, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + + var releaseId = await GetReleaseIdByNumber( + projectId, + environmentId, + bucketId, + releaseNum, + cancellationToken); + + var ccdRelease = new CcdUpdateReleaseRequest(notes); + var response = await m_ReleasesApi.UpdateReleaseEnvAsync( + environmentId, + bucketId, + releaseId, + projectId, + ccdRelease, + 0, + cancellationToken); + return response; + } + + public async Task GetReleaseAsync( + string projectId, + string environmentId, + string bucketId, + int releaseNum, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + + var releaseId = await GetReleaseIdByNumber( + projectId, + environmentId, + bucketId, + releaseNum, + cancellationToken); + + var response = await m_ReleasesApi.GetReleaseEnvAsync( + environmentId, + bucketId, + releaseId, + projectId, + 0, + cancellationToken); + return response; + } + + public async Task GetReleaseByBadgeNameAsync( + string projectId, + string environmentId, + string bucketId, + string badgeName, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + + var response = await m_ReleasesApi.GetReleaseByBadgeEnvAsync( + environmentId, + bucketId, + badgeName, + projectId, + 0, + cancellationToken); + return response; + } + + public async Task>> ListReleaseAsync( + string projectId, + string environmentId, + string bucketId, + int? page, + int? perPage, + string? releaseNum, + string? promotedFromBucket, + string? promotedFromRelease, + string? badges, + string? notes, + string? sortBy, + string? sortOrder, + CancellationToken cancellationToken = default) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + + return await m_ReleasesApi.GetReleasesEnvWithHttpInfoAsync( + environmentId, + bucketId, + projectId, + page, + perPage, + releaseNum, + notes, + promotedFromBucket, + promotedFromRelease, + badges, + sortBy, + sortOrder, + 0, + cancellationToken); + } + + public async Task GetReleaseIdByNumber( + string projectId, + string environmentId, + string bucketId, + int releaseNum, + CancellationToken cancellationToken) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + + var result = await m_ReleasesApi.GetReleasesEnvAsync( + environmentId, + bucketId, + projectId, + 1, + 1, + releaseNum.ToString(), + null, + null, + null, + null, + null, + null, + 0, + cancellationToken); + + if (result.Count == 0) + throw new CliException($"No release exists with the number: {releaseNum}", ExitCode.HandledError); + + return result[0].Releaseid.ToString(); + } + + public async Task AuthorizeServiceAsync(CancellationToken cancellationToken = default) + { + var token = await m_AuthenticationService.GetAccessTokenAsync(cancellationToken); + m_ReleasesApi.Configuration.DefaultHeaders.SetAccessTokenHeader(token); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/SynchronizationService.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/SynchronizationService.cs new file mode 100644 index 0000000..d90680d --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/SynchronizationService.cs @@ -0,0 +1,642 @@ +using System.IO.Abstractions; +using System.Text.RegularExpressions; +using Microsoft.Extensions.Logging; +using Unity.Services.Cli.CloudContentDelivery.Model; +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Cli.ServiceAccountAuthentication; +using Unity.Services.Cli.ServiceAccountAuthentication.Token; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Api; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Model; + +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +public class SynchronizationService : ISynchronizationService +{ + readonly IServiceAccountAuthenticationService m_AuthenticationService; + readonly IContentDeliveryValidator m_ContentDeliveryValidator; + readonly IEntriesApi m_EntriesApi; + readonly IFileSystem m_FileSystem; + readonly IUploadContentClient m_UploadContentClient; + const double k_BytesInMb = 1048576.0; + const int k_MaximumPageSizeAllowed = 100; + + public SynchronizationService( + IEntriesApi entriesApi, + IUploadContentClient uploadContentClient, + IFileSystem fileSystem, + IServiceAccountAuthenticationService authenticationService, + IContentDeliveryValidator contentDeliveryValidator) + { + m_EntriesApi = entriesApi; + m_UploadContentClient = uploadContentClient; + m_FileSystem = fileSystem; + m_AuthenticationService = authenticationService; + m_ContentDeliveryValidator = contentDeliveryValidator; + } + + internal static async Task RespectRateLimitAsync( + Multimap headers, + SharedRateLimitStatus rateLimitStatus, + CancellationToken cancellationToken) + { + if (!headers.TryGetValue("unity-ratelimit", out var rateLimitValues)) + return; + + var rateLimit = rateLimitValues.FirstOrDefault(); + if (string.IsNullOrEmpty(rateLimit)) + return; + + var limits = rateLimit.Split(';'); + foreach (var limit in limits) + { + var parts = limit.Split(','); + var remainingPart = parts.FirstOrDefault(p => p.Contains("remaining")); + var resetPart = parts.FirstOrDefault(p => p.Contains("reset")); + + if (remainingPart == null || resetPart == null) + continue; + + var remaining = int.Parse(remainingPart.Split('=')[1]); + var resetInSeconds = int.Parse(resetPart.Split('=')[1]); + if (remaining == 0) + { + var resetTime = TimeSpan.FromSeconds(resetInSeconds); + rateLimitStatus.UpdateRateLimit(true, resetTime); + await Task.Delay(resetTime, cancellationToken); + rateLimitStatus.UpdateRateLimit(false, TimeSpan.Zero); + return; + } + } + } + + public async Task> FetchAllEntriesAsync( + string projectId, + string environmentId, + string bucketId, + CancellationToken cancellationToken) + { + var rateLimitStatus = new SharedRateLimitStatus(); + var allEntries = new List(); + Guid? lastEntryId = null; + var hasMorePages = true; + while (hasMorePages) + { + var pageResponse = await FetchPageAsync( + projectId, + environmentId, + bucketId, + lastEntryId, + lastEntryId == null ? 10 : k_MaximumPageSizeAllowed, + rateLimitStatus, + cancellationToken); + + if (pageResponse.Data.Count == 0) + { + hasMorePages = false; + } + else + { + allEntries.AddRange(pageResponse.Data.Where(entry => entry.Complete)); + lastEntryId = pageResponse.Data.Last().Entryid; + } + } + + return allEntries; + } + + async Task>> FetchPageAsync( + string projectId, + string environmentId, + string bucketId, + Guid? lastEntryId, + int perPage, + SharedRateLimitStatus rateLimitStatus, + CancellationToken cancellationToken) + { + if (rateLimitStatus.IsRateLimited) await Task.Delay(rateLimitStatus.ResetTime, cancellationToken); + + var response = await m_EntriesApi.GetEntriesEnvWithHttpInfoAsync( + environmentId, + bucketId, + projectId, + null, + lastEntryId, + perPage, + null, + null, + null, + null, + null, + null, + 0, + cancellationToken); + + await RespectRateLimitAsync(response.Headers, rateLimitStatus, cancellationToken); + return response; + + } + + public async Task CalculateSynchronization( + string projectId, + string environmentId, + string bucketId, + string localFolder, + string? exclusionPattern, + bool delete, + List? labels, + object? metadata, + CancellationToken cancellationToken) + { + await AuthorizeServiceAsync(cancellationToken); + m_ContentDeliveryValidator.ValidateProjectIdAndEnvironmentId(projectId, environmentId); + m_ContentDeliveryValidator.ValidateBucketId(bucketId); + + // Get Remote files + var remoteEntries = await FetchAllEntriesAsync( + projectId, + environmentId, + bucketId, + cancellationToken); + + // Get Local files + var localFiles = GetFilesFromDir(localFolder, exclusionPattern); + + // Synchronize files (Diff between Local and Remote) + return CalculateDifference( + projectId, + environmentId, + bucketId, + localFolder, + delete, + remoteEntries, + localFiles, + labels, + metadata); + + } + + public SyncResult CalculateDifference( + string projectId, + string environmentId, + string bucketId, + string localFolder, + bool deleteIfFileNotPresentInLocalFolder, + List remoteEntries, + HashSet localFiles, + List? labels, + object? metadata) + { + + var syncResult = new SyncResult(); + + foreach (var remoteEntry in remoteEntries) + { + var path = remoteEntry.Path; + + if (localFiles.Contains(path)) + { + var filePath = Path.Combine(localFolder, path); + try + { + using (var filestream = m_FileSystem.File.OpenRead(filePath)) + { + var contentType = m_UploadContentClient.GetContentType(filePath); + var contentSize = m_UploadContentClient.GetContentSize(filestream); + var contentHash = m_UploadContentClient.GetContentHash(filestream); + + var currentEntry = new SyncEntry( + path, + environmentId, + bucketId, + projectId, + contentSize, + contentType, + contentHash, + remoteEntry.Entryid.ToString(), + remoteEntry.CurrentVersionid.ToString(), + labels, + metadata); + + if (contentSize != remoteEntry.ContentSize || + !string.Equals(remoteEntry.ContentType, contentType, StringComparison.OrdinalIgnoreCase) || + !string.Equals(remoteEntry.ContentHash, contentHash, StringComparison.OrdinalIgnoreCase)) + syncResult.EntriesToUpdate.Add(currentEntry); + else + syncResult.EntriesToSkip.Add(currentEntry); + } + + // Remove from local files + localFiles.Remove(path); + } + catch (Exception ex) + { + throw new CliException($"Error processing entry '{filePath}': {ex.Message}", ExitCode.HandledError); + } + } + else if (deleteIfFileNotPresentInLocalFolder) + { + // Remote files not present in local folder are marked to be deleted. + syncResult.EntriesToDelete.Add( + new SyncEntry( + path, + environmentId, + bucketId, + projectId, + null, + "", + "", + remoteEntry.Entryid.ToString(), + remoteEntry.CurrentVersionid.ToString())); + } + else + { + // Remote files not present in local folder are to be skipped. + var entryToSkip = new SyncEntry( + path) + { + EntryId = remoteEntry.Entryid.ToString(), + VersionId = remoteEntry.CurrentVersionid.ToString() + }; + syncResult.EntriesToSkip.Add( + entryToSkip + ); + } + } + + // Add remaining local files to EntriesToAdd. + foreach (var path in localFiles) + { + var filePath = Path.Combine(localFolder, path); + using var filestream = m_FileSystem.File.OpenRead(filePath); + var contentSize = m_UploadContentClient.GetContentSize(filestream); + var contentType = m_UploadContentClient.GetContentType(filePath); + var contentHash = m_UploadContentClient.GetContentHash(filestream); + + syncResult.EntriesToAdd.Add( + new SyncEntry( + path, + environmentId, + bucketId, + projectId, + contentSize, + contentType, + contentHash, + null, + null, + labels, + metadata + )); + } + + return syncResult; + } + + public async Task> ProcessSynchronization( + ILogger logger, + bool verbose, + SyncResult syncResult, + string localFolder, + int retryCount, + int maxConcurrentRequests, + int retryDelayMilliseconds, + CancellationToken cancellationToken) + { + await AuthorizeServiceAsync(cancellationToken); + var rateLimitStatus = new SharedRateLimitStatus(); + var semaphore = new SemaphoreSlim(maxConcurrentRequests); + + + var addBatches = CreateBatches(syncResult.EntriesToAdd, 20); + var updateBatches = CreateBatches(syncResult.EntriesToUpdate, 20); + var deleteBatches = CreateBatches(syncResult.EntriesToDelete, 20); + + var tasks = addBatches.Select( + batch => ProcessBatchAddOrUpdateEntryAsync( + logger, + verbose, + batch, + localFolder, + retryCount, + retryDelayMilliseconds, + semaphore, + rateLimitStatus, + cancellationToken)) + .ToList(); + + tasks.AddRange( + updateBatches.Select( + batch => ProcessBatchAddOrUpdateEntryAsync( + logger, + verbose, + batch, + localFolder, + retryCount, + retryDelayMilliseconds, + semaphore, + rateLimitStatus, + cancellationToken)) + .ToList()); + + tasks.AddRange( + deleteBatches.Select( + batch => ProcessBatchDeleteEntryAsync( + logger, + verbose, + batch, + retryCount, + retryDelayMilliseconds, + semaphore, + rateLimitStatus, + cancellationToken)) + .ToList()); + + try + { + var results = await Task.WhenAll(tasks); + CollectSyncEntriesVersion(syncResult, results, out var releaseEntries); + return releaseEntries; + } + catch (Exception ex) + { + throw new CliException( + $"At least one of the task failed during the synchronization: {ex.Message}", + ExitCode.HandledError); + } + + } + + static List> CreateBatches(List entries, int batchSize) + { + var batches = new List>(); + for (int i = 0; i < entries.Count; i += batchSize) + { + batches.Add(entries.GetRange(i, Math.Min(batchSize, entries.Count - i))); + } + return batches; + } + + + static void CollectSyncEntriesVersion( + SyncResult syncResult, + List[] results, + out List releaseEntries) + { + + releaseEntries = new List(); + foreach (var taskResult in results) releaseEntries.AddRange(taskResult); + releaseEntries.AddRange( + syncResult.EntriesToSkip + .Select( + entry => new CcdCreateReleaseRequestEntriesInner( + new Guid(entry.EntryId), + new Guid(entry.VersionId)))); + } + + async Task> ProcessBatchAddOrUpdateEntryAsync( + ILogger logger, + bool verbose, + List entryBatch, + string localFolder, + int retryCount, + int retryDelayMilliseconds, + SemaphoreSlim semaphore, + SharedRateLimitStatus rateLimitStatus, + CancellationToken cancellationToken) + { + + var releaseEntries = new List(); + await ThrottledRetryPolicyAsync( + logger, + verbose, + retryCount, + retryDelayMilliseconds, + semaphore, + async () => + { + + if (entryBatch.Count == 0) + return; + + var ccdCreateOrUpdateEntryBatchRequestInner = entryBatch.Select( + entry => new CcdCreateOrUpdateEntryBatchRequestInner( + entry.ContentHash, + (int)entry.ContentSize!, + entry.ContentType, + entry.Labels ?? new List(), + entry.Metadata ?? "", + entry.Path)) + .ToList(); + + if (rateLimitStatus.IsRateLimited) await Task.Delay(rateLimitStatus.ResetTime, cancellationToken); + + var createdEntriesResponse = await m_EntriesApi.CreateOrUpdateEntryBatchEnvWithHttpInfoAsync( + entryBatch.First().EnvironmentId, + entryBatch.First().BucketId, + entryBatch.First().ProjectId, + ccdCreateOrUpdateEntryBatchRequestInner, + 0, + cancellationToken); + + + await RespectRateLimitAsync(createdEntriesResponse.Headers, rateLimitStatus, cancellationToken); + + var uploadSemaphore = new SemaphoreSlim(10); + var tasks = new List(); + + foreach (var createdEntry in createdEntriesResponse.Data) + { + await uploadSemaphore.WaitAsync(); + tasks.Add(Task.Run(async () => + { + try + { + var filePath = Path.Combine(localFolder, createdEntry.Path); + await using var filestream = m_FileSystem.File.OpenRead(filePath); + var response = await m_UploadContentClient.UploadContentToCcd( + createdEntry.SignedUrl, + filestream, + cancellationToken); + if (response.IsSuccessStatusCode) + releaseEntries.Add( + new CcdCreateReleaseRequestEntriesInner( + createdEntry.Entryid, + createdEntry.CurrentVersionid)); + else + throw new Exception($"Error syncing entry {createdEntry.Path}: {response.ReasonPhrase}."); + } + finally + { + uploadSemaphore.Release(); + } + })); + } + await Task.WhenAll(tasks); + + // Add small throttling between batches + await Task.Delay(500, cancellationToken); + + }); + return releaseEntries; + } + + async Task> ProcessBatchDeleteEntryAsync( + ILogger logger, + bool verbose, + List entryBatch, + int retryCount, + int retryDelayMilliseconds, + SemaphoreSlim semaphore, + SharedRateLimitStatus rateLimitStatus, + CancellationToken cancellationToken) + { + await ThrottledRetryPolicyAsync( + logger, + verbose, + retryCount, + + retryDelayMilliseconds, + semaphore, + async () => + { + if (entryBatch.Count == 0) + return; + + var listEntries = entryBatch.Select( + entry => new CcdDeleteEntryBatchRequestInner( + new Guid(entry.EntryId))) + .ToList(); + + if (rateLimitStatus.IsRateLimited) await Task.Delay(rateLimitStatus.ResetTime, cancellationToken); + + var response = await m_EntriesApi.DeleteEntryBatchEnvWithHttpInfoAsync( + entryBatch.First().EnvironmentId, + entryBatch.First().BucketId, + entryBatch.First().ProjectId, + listEntries, + 0, + cancellationToken); + await RespectRateLimitAsync(response.Headers, rateLimitStatus, cancellationToken); + + }); + return new List(); + } + + static async Task ThrottledRetryPolicyAsync( + ILogger logger, + bool verbose, + int retryCount, + int retryDelayMilliseconds, + SemaphoreSlim semaphore, + Func action) + { + + var currentRetry = 0; + while (currentRetry <= retryCount) + try + { + await semaphore.WaitAsync(); + await action(); + + if (verbose && currentRetry >= 1) + logger.LogWarning( + $"Synchronization Operation {Math.Abs(action.GetHashCode())} succeeded after retry {currentRetry + 1}/{retryCount}."); + break; + } + catch (Exception ex) + { + currentRetry++; + if (currentRetry > retryCount) + throw new CliException( + $"Error, the synchronization failed: {ex.Message}", + ExitCode.HandledError); + if (verbose) + logger.LogWarning( + $"Synchronization operation {Math.Abs(action.GetHashCode())} failed (attempt {currentRetry}/{retryCount}): {ex.Message}"); + + await Task.Delay(retryDelayMilliseconds); + } + finally + { + semaphore.Release(); + } + } + + public static double CalculateUploadSpeed(double totalDataInMegabytes, double timeInSeconds) + { + var totalDataInMegabits = totalDataInMegabytes * 8; + var uploadSpeedMbps = totalDataInMegabits / timeInSeconds; + return uploadSpeedMbps; + } + + public static IOperationSummary CalculateOperationSummary( + bool verbose, + SyncResult syncResult, + double syncProcessTime, + CcdGetBucket200ResponseLastRelease? release, + CcdGetBucket200ResponseLastReleaseBadgesInner? badge) + { + if (verbose) + { + long totalUploadedSizeInBytes = 0; + var totalFilesUploaded = 0; + foreach (var entry in syncResult.EntriesToAdd.Concat(syncResult.EntriesToUpdate)) + { + totalUploadedSizeInBytes += entry.ContentSize ?? 0; + totalFilesUploaded++; + } + + var totalDataInMegabytes = totalUploadedSizeInBytes / k_BytesInMb; + var uploadSpeed = CalculateUploadSpeed(totalDataInMegabytes, syncProcessTime); + + return new LongOperationSummary( + syncResult, + true, + syncProcessTime, + totalDataInMegabytes, + uploadSpeed, + totalFilesUploaded, + release, + badge + ); + } + else + { + return new ShortOperationSummary( + syncResult, + true, + syncProcessTime, + release, + badge + ); + } + } + + + public HashSet GetFilesFromDir( + string directoryPath, + string? exclusionPattern) + { + if (!m_FileSystem.Directory.Exists(directoryPath)) + throw new CliException($"Directory '{directoryPath}' does not exist.", ExitCode.HandledError); + + var files = m_FileSystem.Directory.EnumerateFiles(directoryPath, "*", SearchOption.AllDirectories); + if (!string.IsNullOrEmpty(exclusionPattern)) + { + var excludePatternRegex = new Regex( + exclusionPattern, + RegexOptions.CultureInvariant, + TimeSpan.FromSeconds(2)); + files = files.Where(filePath => !excludePatternRegex.IsMatch(filePath)); + } + + return new HashSet( + files.Select(filePath => m_FileSystem.Path.GetRelativePath(directoryPath, filePath))); + } + + public async Task AuthorizeServiceAsync(CancellationToken cancellationToken = default) + { + var token = await m_AuthenticationService.GetAccessTokenAsync(cancellationToken); + m_EntriesApi.Configuration.DefaultHeaders.SetAccessTokenHeader(token); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/UploadContentClient.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/UploadContentClient.cs new file mode 100644 index 0000000..49719e7 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Service/UploadContentClient.cs @@ -0,0 +1,59 @@ +using System.IO.Abstractions; +using System.Security.Cryptography; + +namespace Unity.Services.Cli.CloudContentDelivery.Service; + +public class UploadContentClient : IUploadContentClient +{ + readonly HttpClient m_HttpClient; + + public UploadContentClient(HttpClient httpClient) + { + m_HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + m_HttpClient.Timeout = TimeSpan.FromSeconds(900); + } + + public string GetContentType(string localPath) + { + try + { + return MimeMapping.MimeUtility.GetMimeMapping(localPath); + } + catch (Exception) + { + return "application/octet-stream"; + } + } + + public Task UploadContentToCcd( + string signedUrl, + FileSystemStream filestream, + CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage + { + Method = HttpMethod.Put, + RequestUri = new Uri(signedUrl) + }; + + filestream.Seek(0, SeekOrigin.Begin); + var streamContent = new StreamContent(filestream); + + streamContent.Headers.Add("Content-Type", GetContentType(filestream.Name)); + streamContent.Headers.Add("Content-Length", filestream.Length.ToString()); + request.Content = streamContent; + + return m_HttpClient.PutAsync(signedUrl, streamContent, cancellationToken); + } + + public string GetContentHash(FileSystemStream filestream) + { + var md5 = MD5.Create(); + return BitConverter.ToString(md5.ComputeHash(filestream)).Replace("-", string.Empty).ToLower(); + } + + public long GetContentSize(FileSystemStream filestream) + { + return filestream.Length; + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Unity.Services.Cli.CloudContentDelivery.csproj b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Unity.Services.Cli.CloudContentDelivery.csproj new file mode 100644 index 0000000..3990e23 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Unity.Services.Cli.CloudContentDelivery.csproj @@ -0,0 +1,32 @@ + + + net8.0 + 10 + enable + enable + true + + + + <_Parameter1>$(AssemblyName).UnitTest + + + <_Parameter1>DynamicProxyGenAssembly2 + + + + + + + + + + + + + + + + $(DefineConstants);$(ExtraDefineConstants) + + \ No newline at end of file diff --git a/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Utils/CcdUtils.cs b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Utils/CcdUtils.cs new file mode 100644 index 0000000..35c8bb5 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.CloudContentDelivery/Utils/CcdUtils.cs @@ -0,0 +1,50 @@ +using Newtonsoft.Json; +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client; + +namespace Unity.Services.Cli.CloudContentDelivery.Utils; + +public static class CcdUtils +{ + + public static string? GetPaginationInformation(ApiResponse> result) + { + if (!result.Headers.TryGetValue("Content-Range", out var contentRangeValues)) return null; + var contentRange = contentRangeValues.FirstOrDefault(); + return $"Listing {contentRange}"; + } + + public static object? ParseMetadata(string metadata) + { + try + { + var result = JsonConvert.DeserializeObject(metadata); + return result; + } + catch (JsonException ex) + { + throw new CliException( + $"The metadata could not be parsed successfully: {ex.Message}", + ExitCode.HandledError); + } + } + + public static void ValidateBucketIdIsPresent(string? bucketId) + { + if (bucketId == null || !Guid.TryParse(bucketId, out _)) + { + throw new CliException( + @"A valid bucket-name is required to execute this command. + +You can specify the bucket-name using one of the following methods: + 1. Set the bucket-name in your configuration once for all future commands: + ugs config set bucket-name + 2. Include the -b option when running the command. + + 3. Set the UGS_CLI_BUCKET_NAME environment variable to the desired bucket-name for all future commands.", + ExitCode.HandledError); + + } + } + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/CommonModuleTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/CommonModuleTests.cs index 4787c1c..bff48f3 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/CommonModuleTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/CommonModuleTests.cs @@ -88,8 +88,8 @@ public void CreateAndRegisterLoadingIndicatorServiceSucceeds() [Test] public void CreateAndRegisterCliPromptServiceSucceeds() { - CommonModule.CreateAndRegisterCliPromptService(m_MockedServiceCollection!.Object); - Assert.AreEqual(1, m_Services!.Count); + CommonModule.CreateAndRegisterCliPromptService(m_MockedServiceCollection!.Object, null!, false); + Assert.AreEqual(2, m_Services!.Count); } [Test] @@ -108,7 +108,7 @@ public void CreateAndRegisterProgressBarServiceNoQuietAliasSetsConsole() var console = AnsiConsole.Create(new AnsiConsoleSettings()); CommonModule.CreateAndRegisterProgressBarService(m_MockedServiceCollection!.Object, console); ProgressBar progressBar = (ProgressBar)m_Services![0].ImplementationInstance!; - Assert.IsNotNull(progressBar.k_AnsiConsole); + Assert.IsNotNull(progressBar.AnsiConsole); } [Test] @@ -117,7 +117,7 @@ public void CreateAndRegisterProgressBarServiceQuietAliasSetsConsoleToNull() IAnsiConsole? console = null; CommonModule.CreateAndRegisterProgressBarService(m_MockedServiceCollection!.Object, console); ProgressBar progressBar = (ProgressBar)m_Services![0].ImplementationInstance!; - Assert.IsNull(progressBar.k_AnsiConsole); + Assert.IsNull(progressBar.AnsiConsole); } @@ -145,7 +145,7 @@ public void CreateTelemetrySender_SetsCorrectBasePath() public void CreateTelemetrySender_SetsBaseProductTags() { var telemetrySender = CommonModule.CreateTelemetrySender(m_MockSystemEnvironmentProvider.Object); - StringAssert.AreEqualIgnoringCase(telemetrySender.ProductTags[TagKeys.ProductName], CommonModule.m_CliProductName); + StringAssert.AreEqualIgnoringCase(telemetrySender.ProductTags[TagKeys.ProductName], CommonModule.cliProductName); StringAssert.AreEqualIgnoringCase(telemetrySender.ProductTags[TagKeys.CliVersion], TelemetryConfigurationProvider.GetCliVersion()); } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Console/CliPromptTest.cs b/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Console/CliPromptTest.cs index abebccf..04169ea 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Console/CliPromptTest.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Console/CliPromptTest.cs @@ -22,7 +22,7 @@ public void SetUp() public async Task PromptAsyncSucceed() { const string expectedString = "foo"; - var prompt = new CliPrompt(m_MockAnsiConsole.Object, false); + var prompt = new ConsolePrompt(m_MockAnsiConsole.Object, false); m_MockPrompt.Setup(p => p.ShowAsync(m_MockAnsiConsole.Object, CancellationToken.None)) .ReturnsAsync(expectedString); diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Console/ConsoleTableTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Console/ConsoleTableTests.cs new file mode 100644 index 0000000..364d883 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Console/ConsoleTableTests.cs @@ -0,0 +1,35 @@ +using Moq; +using NUnit.Framework; +using Spectre.Console; +using Spectre.Console.Rendering; +using Unity.Services.Cli.Common.Console; + +namespace Unity.Services.Cli.Common.UnitTest.Console; + +[TestFixture] +public class ConsoleTableTests +{ + Mock? m_TestConsole; + ConsoleTable? m_Table; + + [SetUp] + public void SetUp() + { + m_TestConsole = new(); + m_Table = new(m_TestConsole.Object, false, false); + } + + [Test] + public void DrawingTableCallsSpectreConsoleWrite() + { + m_Table!.DrawTable(); + m_TestConsole!.Verify(c => c.Write(It.IsAny())); + } + + [Test] + public void TableAddMultipleColumnsAddsColumns() + { + m_Table!.AddColumns(new Text(""), new Text("")); + Assert.AreEqual(2, m_Table.GetColumns().Count); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Handlers/DeleteHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Handlers/DeleteHandlerTests.cs index 147065f..b9ef58c 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Handlers/DeleteHandlerTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Handlers/DeleteHandlerTests.cs @@ -13,14 +13,14 @@ namespace Unity.Services.Cli.Common.UnitTest.Handlers; [TestFixture] class DeleteHandlerTests { - MockHelper mockHelper = new(); + MockHelper m_MockHelper = new(); Mock m_MockSystemEnvironmentProvider = new(); [SetUp] public void Setup() { - mockHelper.MockLogger.Reset(); - mockHelper.MockConfiguration.Reset(); + m_MockHelper.MockLogger.Reset(); + m_MockHelper.MockConfiguration.Reset(); m_MockSystemEnvironmentProvider.Reset(); } @@ -33,10 +33,10 @@ public void DeleteAsync_NotHavingForceOptionThrows() UseForce = false }; - Assert.ThrowsAsync(() => DeleteHandler.DeleteAsync(input, mockHelper.MockConfiguration.Object, - mockHelper.MockLogger.Object, m_MockSystemEnvironmentProvider.Object, CancellationToken.None)); + Assert.ThrowsAsync(() => DeleteHandler.DeleteAsync(input, m_MockHelper.MockConfiguration.Object, + m_MockHelper.MockLogger.Object, m_MockSystemEnvironmentProvider.Object, CancellationToken.None)); - mockHelper.MockConfiguration.Verify(ex => ex + m_MockHelper.MockConfiguration.Verify(ex => ex .DeleteConfigArgumentsAsync(It.IsAny(), It.IsAny()), Times.Never); } @@ -46,14 +46,14 @@ public void DeleteAsync_HavingBothKeysAndAllKeysOptionSpecifiedThrows() ConfigurationInput input = new ConfigurationInput { TargetAllKeys = true, - Keys = new []{""}, + Keys = new[] { "" }, UseForce = true }; - Assert.ThrowsAsync(() => DeleteHandler.DeleteAsync(input, mockHelper.MockConfiguration.Object, - mockHelper.MockLogger.Object, m_MockSystemEnvironmentProvider.Object, CancellationToken.None)); + Assert.ThrowsAsync(() => DeleteHandler.DeleteAsync(input, m_MockHelper.MockConfiguration.Object, + m_MockHelper.MockLogger.Object, m_MockSystemEnvironmentProvider.Object, CancellationToken.None)); - mockHelper.MockConfiguration.Verify(ex => ex + m_MockHelper.MockConfiguration.Verify(ex => ex .DeleteConfigArgumentsAsync(It.IsAny(), It.IsAny()), Times.Never); } @@ -61,10 +61,10 @@ public void DeleteAsync_HavingBothKeysAndAllKeysOptionSpecifiedThrows() public void DeleteAsync_HavingNoOptionSpecifiedThrows() { Assert.ThrowsAsync(() => DeleteHandler.DeleteAsync(new ConfigurationInput(), - mockHelper.MockConfiguration.Object, mockHelper.MockLogger.Object, m_MockSystemEnvironmentProvider.Object, + m_MockHelper.MockConfiguration.Object, m_MockHelper.MockLogger.Object, m_MockSystemEnvironmentProvider.Object, CancellationToken.None)); - mockHelper.MockConfiguration.Verify(ex => ex + m_MockHelper.MockConfiguration.Verify(ex => ex .DeleteConfigArgumentsAsync(It.IsAny(), It.IsAny()), Times.Never); } @@ -77,13 +77,13 @@ public async Task DeleteAsync_CallsConfigurationAndLogsAllKeysDeleted() UseForce = true }; - await DeleteHandler.DeleteAsync(input, mockHelper.MockConfiguration.Object, - mockHelper.MockLogger.Object, m_MockSystemEnvironmentProvider.Object, CancellationToken.None); + await DeleteHandler.DeleteAsync(input, m_MockHelper.MockConfiguration.Object, + m_MockHelper.MockLogger.Object, m_MockSystemEnvironmentProvider.Object, CancellationToken.None); - TestsHelper.VerifyLoggerWasCalled(mockHelper.MockLogger, LogLevel.Information, null, null, - DeleteHandler.k_DeletedAllKeysMsg); + TestsHelper.VerifyLoggerWasCalled(m_MockHelper.MockLogger, LogLevel.Information, null, null, + DeleteHandler.deletedAllKeysMsg); - mockHelper.MockConfiguration.Verify(ex => ex + m_MockHelper.MockConfiguration.Verify(ex => ex .DeleteConfigArgumentsAsync(It.IsAny(), It.IsAny()), Times.Once); } @@ -92,17 +92,17 @@ public async Task DeleteAsync_CallsConfigurationAndLogsSpecifiedKeysDeleted() { ConfigurationInput input = new ConfigurationInput { - Keys = new []{""}, + Keys = new[] { "" }, UseForce = true }; - await DeleteHandler.DeleteAsync(input, mockHelper.MockConfiguration.Object, - mockHelper.MockLogger.Object, m_MockSystemEnvironmentProvider.Object, CancellationToken.None); + await DeleteHandler.DeleteAsync(input, m_MockHelper.MockConfiguration.Object, + m_MockHelper.MockLogger.Object, m_MockSystemEnvironmentProvider.Object, CancellationToken.None); - TestsHelper.VerifyLoggerWasCalled(mockHelper.MockLogger, LogLevel.Information, null, null, - DeleteHandler.k_DeletedSpecifiedKeysMsg); + TestsHelper.VerifyLoggerWasCalled(m_MockHelper.MockLogger, LogLevel.Information, null, null, + DeleteHandler.deletedSpecifiedKeysMsg); - mockHelper.MockConfiguration.Verify(ex => ex + m_MockHelper.MockConfiguration.Verify(ex => ex .DeleteConfigArgumentsAsync(It.IsAny(), It.IsAny()), Times.Once); } @@ -111,7 +111,7 @@ public async Task DeleteAsync_ChecksEnvironmentVariablesAndLogsWarningWhenValueS { ConfigurationInput input = new ConfigurationInput { - Keys = new []{Keys.ConfigKeys.ProjectId}, + Keys = new[] { Keys.ConfigKeys.ProjectId }, UseForce = true }; @@ -119,12 +119,12 @@ public async Task DeleteAsync_ChecksEnvironmentVariablesAndLogsWarningWhenValueS m_MockSystemEnvironmentProvider.Setup(ex => ex .GetSystemEnvironmentVariable(It.IsAny(), out errorMsg)).Returns("value"); - await DeleteHandler.DeleteAsync(input, mockHelper.MockConfiguration.Object, - mockHelper.MockLogger.Object, m_MockSystemEnvironmentProvider.Object, CancellationToken.None); + await DeleteHandler.DeleteAsync(input, m_MockHelper.MockConfiguration.Object, + m_MockHelper.MockLogger.Object, m_MockSystemEnvironmentProvider.Object, CancellationToken.None); m_MockSystemEnvironmentProvider.Verify(ex => ex .GetSystemEnvironmentVariable(It.IsAny(), out errorMsg), Times.Once); - TestsHelper.VerifyLoggerWasCalled(mockHelper.MockLogger, LogLevel.Warning); + TestsHelper.VerifyLoggerWasCalled(m_MockHelper.MockLogger, LogLevel.Warning); } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Unity.Services.Cli.Common.UnitTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Unity.Services.Cli.Common.UnitTest.csproj index faaa8aa..a5189ba 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Unity.Services.Cli.Common.UnitTest.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Unity.Services.Cli.Common.UnitTest.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 false enable enable diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Validator/ConfigurationValidatorTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Validator/ConfigurationValidatorTests.cs index e2345e9..57241ad 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Validator/ConfigurationValidatorTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common.UnitTest/Validator/ConfigurationValidatorTests.cs @@ -97,6 +97,7 @@ public void IsKeyValidValidKeyTest(string key) [TestCase(Models.Keys.ConfigKeys.ProjectId, "invalid-value")] [TestCase(Models.Keys.ConfigKeys.EnvironmentId, "invalid-value")] [TestCase(Models.Keys.ConfigKeys.EnvironmentName, " ")] + [TestCase(Models.Keys.ConfigKeys.BucketName, " ")] public void InvalidConfigThrowTest(string key, string value) { Assert.Throws(() => m_ConfigurationValidator.ThrowExceptionIfConfigInvalid(key, value)); diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/CommonModule.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/CommonModule.cs index 4f836cf..732f154 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/CommonModule.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/CommonModule.cs @@ -25,7 +25,7 @@ namespace Unity.Services.Cli.Common; public static class CommonModule { - public const string m_CliProductName = "com.unity.ugs-cli"; + public const string cliProductName = "com.unity.ugs-cli"; public static CommandLineBuilder UseTreePrinter(this CommandLineBuilder builder) { var printTreeFlag = new Option("--print-tree") @@ -60,8 +60,8 @@ public static void ConfigureCommonServices(IHostBuilder hostBuilder, Logger logg IAnsiConsole ansiConsole, IAnalyticEventFactory analyticEventFactory) { var parseResult = hostBuilder.GetInvocationContext().ParseResult; - bool silentAnsiConsole = parseResult.GetValueForOption(CommonInput.QuietOption) || - parseResult.GetValueForOption(CommonInput.JsonOutputOption); + bool outputIsJson = parseResult.GetValueForOption(CommonInput.JsonOutputOption); + bool silentAnsiConsole = parseResult.GetValueForOption(CommonInput.QuietOption) || outputIsJson; var allDefinedTypesInDomain = AppDomain.CurrentDomain .GetAssemblies() .SelectMany(x => x.DefinedTypes); @@ -76,7 +76,8 @@ public static void ConfigureCommonServices(IHostBuilder hostBuilder, Logger logg CreateAndRegisterProgressBarService(serviceCollection, usedConsole)); hostBuilder.ConfigureServices(serviceCollection => CreateAndRegisterLoadingIndicatorService(serviceCollection, usedConsole)); - hostBuilder.ConfigureServices(CreateAndRegisterCliPromptService); + hostBuilder.ConfigureServices(serviceCollection => + CreateAndRegisterCliPromptService(serviceCollection, ansiConsole, outputIsJson)); hostBuilder.ConfigureServices(CreateAndRegisterCliAnalyticsSenderService); hostBuilder.ConfigureServices(CreateAndRegisterCliProcessService); } @@ -134,14 +135,14 @@ internal static void CreateAndRegisterLoadingIndicatorService(IServiceCollection serviceCollection.AddSingleton(new LoadingIndicator(ansiConsole)); } - internal static void CreateAndRegisterCliPromptService(IServiceCollection serviceCollection) + internal static void CreateAndRegisterCliPromptService( + IServiceCollection serviceCollection, + IAnsiConsole ansiConsole, + bool outputIsJson) { - var settings = new AnsiConsoleSettings - { - Interactive = InteractionSupport.Yes - }; - var console = AnsiConsole.Create(settings); - serviceCollection.AddSingleton(new CliPrompt(console, System.Console.IsInputRedirected)); + serviceCollection.AddSingleton(new ConsolePrompt(ansiConsole, System.Console.IsInputRedirected)); + serviceCollection.AddTransient( + _ => new ConsoleTable(ansiConsole, System.Console.IsInputRedirected, outputIsJson)); } public static TelemetrySender CreateTelemetrySender(ISystemEnvironmentProvider systemEnvironmentProvider) @@ -156,7 +157,7 @@ public static TelemetrySender CreateTelemetrySender(ISystemEnvironmentProvider s }; var productTags = new Dictionary { - [TagKeys.ProductName] = m_CliProductName, + [TagKeys.ProductName] = cliProductName, [TagKeys.CliVersion] = TelemetryConfigurationProvider.GetCliVersion() }; @@ -173,7 +174,7 @@ public static TelemetrySender CreateTelemetrySender(ISystemEnvironmentProvider s internal static void CreateAndRegisterCliAnalyticsSenderService(IServiceCollection serviceCollection) { serviceCollection.AddAnalytics(((x, _) => - x.WithSourceName(m_CliProductName) + x.WithSourceName(cliProductName) .WithDefaultUnityBigQueryExporter() .WithCommonHeader(new Dictionary { diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Handlers/DeleteHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Handlers/DeleteHandler.cs index d46a212..1774bbf 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Handlers/DeleteHandler.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Handlers/DeleteHandler.cs @@ -8,16 +8,16 @@ namespace Unity.Services.Cli.Common.Handlers; static class DeleteHandler { - internal const string k_DeletedAllKeysMsg = "All keys were deleted from local configuration."; - internal const string k_DeletedSpecifiedKeysMsg = "Specified keys were deleted from local configuration."; - internal const string k_UnsupportedOptionCombinationErrorMsg = + internal const string deletedAllKeysMsg = "All keys were deleted from local configuration."; + internal const string deletedSpecifiedKeysMsg = "Specified keys were deleted from local configuration."; + internal const string unsupportedOptionCombinationErrorMsg = $"Having both {ConfigurationInput.KeysLongAlias} and {ConfigurationInput.TargetAllKeysLongAlias} options " + "simultaneously is unsupported."; - internal const string k_NoOptionErrorMsg = + internal const string noOptionErrorMsg = $"Specify configuration keys to delete by using the {ConfigurationInput.KeysLongAlias} option. To delete " + $"all keys, use the {ConfigurationInput.TargetAllKeysLongAlias} option."; // Temporary, will be changed with EDX-1435 - internal const string k_ForceRequiredErrorMsg = + internal const string forceRequiredErrorMsg = "This is a destructive operation, use the --force option to continue."; public static async Task DeleteAsync( @@ -26,19 +26,19 @@ public static async Task DeleteAsync( { if (input.TargetAllKeys is true && input.Keys is not null && input.Keys.Length is not 0) { - throw new CliException(k_UnsupportedOptionCombinationErrorMsg, ExitCode.HandledError); + throw new CliException(unsupportedOptionCombinationErrorMsg, ExitCode.HandledError); } if (input.TargetAllKeys is null or false && (input.Keys is null || input.Keys.Length is 0)) { - throw new CliException(k_NoOptionErrorMsg, ExitCode.HandledError); + throw new CliException(noOptionErrorMsg, ExitCode.HandledError); } // Temporary, will be changed with EDX-1435 if (!input.UseForce) { - throw new CliException(k_ForceRequiredErrorMsg, ExitCode.HandledError); + throw new CliException(forceRequiredErrorMsg, ExitCode.HandledError); } if (input.TargetAllKeys is true) @@ -46,13 +46,13 @@ public static async Task DeleteAsync( string[] keys = Keys.ConfigKeys.Keys.ToArray(); await service.DeleteConfigArgumentsAsync(keys, cancellationToken); NotifyWhenKeysSetInEnvironmentVariables(environmentProvider, logger, keys); - logger.LogInformation(k_DeletedAllKeysMsg); + logger.LogInformation(deletedAllKeysMsg); } else { await service.DeleteConfigArgumentsAsync(input.Keys!, cancellationToken); NotifyWhenKeysSetInEnvironmentVariables(environmentProvider, logger, input.Keys!); - logger.LogInformation(k_DeletedSpecifiedKeysMsg); + logger.LogInformation(deletedSpecifiedKeysMsg); } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Models/Configuration.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Models/Configuration.cs index 16014bd..19cf59e 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Models/Configuration.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Models/Configuration.cs @@ -12,8 +12,8 @@ public class Configuration [JsonProperty(Keys.ConfigKeys.ProjectId)] public string? CloudProjectId { get; set; } - [JsonProperty(Keys.ConfigKeys.BucketId)] - public string? CloudBucketId { get; set; } + [JsonProperty(Keys.ConfigKeys.BucketName)] + public string? CloudBucketName { get; set; } public string? GetValue(string key) { @@ -50,8 +50,13 @@ public void DeleteValue(string key) /// Get the supported configuration keys /// /// - public static IList GetKeys() => typeof(Configuration) - .GetProperties().Select(GetJsonPropertyName).ToList(); + public static IList GetKeys() + { + return typeof(Configuration) + .GetProperties() + .Select(GetJsonPropertyName) + .ToList(); + } static string? GetJsonPropertyName(PropertyInfo propertyInfo) { diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Models/Keys.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Models/Keys.cs index ce2b24d..831cae1 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Models/Keys.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Models/Keys.cs @@ -11,11 +11,12 @@ public static class Keys /// Readonly dictionary establishing relations between configuration keys and environment keys /// public static readonly ReadOnlyDictionary ConfigEnvironmentPairs - = new(new Dictionary - { - [ConfigKeys.ProjectId] = EnvironmentKeys.ProjectId, - [ConfigKeys.EnvironmentName] = EnvironmentKeys.EnvironmentName, - }); + = new( + new Dictionary + { + [ConfigKeys.ProjectId] = EnvironmentKeys.ProjectId, + [ConfigKeys.EnvironmentName] = EnvironmentKeys.EnvironmentName + }); /// /// Class containing a set of constant keys used for storing and retrieving from configuration @@ -23,11 +24,19 @@ public static readonly ReadOnlyDictionary ConfigEnvironmentPairs public static class ConfigKeys { public const string ProjectId = "project-id"; + public const string EnvironmentName = "environment-name"; + // Environment Id is currently not stored/retrieved but the key is used for validation purposes public const string EnvironmentId = "environment-id"; - public const string BucketId = "bucket-id"; - public static readonly IReadOnlyList Keys = new List { ProjectId, EnvironmentName, BucketId }; + public const string BucketName = "bucket-name"; + + public static readonly IReadOnlyList Keys = new List + { + ProjectId, + EnvironmentName, + BucketName + }; } /// @@ -37,8 +46,10 @@ public static class EnvironmentKeys { public const string ProjectId = "UGS_CLI_PROJECT_ID"; public const string EnvironmentName = "UGS_CLI_ENVIRONMENT_NAME"; - public const string BucketId = "UGS_CLI_BUCKET_ID"; + public const string BucketName = "UGS_CLI_BUCKET_NAME"; + public const string TelemetryDisabled = "UGS_CLI_TELEMETRY_DISABLED"; + // Env variables used for identifying the cicd platform being used public const string RunningOnDocker = "DOTNET_RUNNING_IN_CONTAINER"; public const string RunningOnJenkins = "JENKINS_HOME"; @@ -46,7 +57,12 @@ public static class EnvironmentKeys public const string RunningOnUnityCloudBuild = "IS_BUILDER"; public const string RunningOnYamato = "YAMATO_JOB_ID"; - public static readonly IReadOnlyList Keys = new List { ProjectId, EnvironmentName, TelemetryDisabled }; + public static readonly IReadOnlyList Keys = new List + { + ProjectId, + EnvironmentName, + TelemetryDisabled + }; public static readonly IReadOnlyList cicdKeys = new List { @@ -62,12 +78,13 @@ public static class EnvironmentKeys /// Readonly dictionary establishing relations between cicd environment keys and their display names /// public static readonly ReadOnlyDictionary CicdEnvVarToDisplayNamePair - = new(new Dictionary - { - [EnvironmentKeys.RunningOnDocker] = "Docker", - [EnvironmentKeys.RunningOnJenkins] = "Jenkins", - [EnvironmentKeys.RunningOnGithubActions] = "GithubActions", - [EnvironmentKeys.RunningOnUnityCloudBuild] = "UnityCloudBuild", - [EnvironmentKeys.RunningOnYamato] = "Yamato" - }); + = new( + new Dictionary + { + [EnvironmentKeys.RunningOnDocker] = "Docker", + [EnvironmentKeys.RunningOnJenkins] = "Jenkins", + [EnvironmentKeys.RunningOnGithubActions] = "GithubActions", + [EnvironmentKeys.RunningOnUnityCloudBuild] = "UnityCloudBuild", + [EnvironmentKeys.RunningOnYamato] = "Yamato" + }); } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Validator/ConfigurationValidator.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Validator/ConfigurationValidator.cs index ec9de29..7d1ff55 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Validator/ConfigurationValidator.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Configuration/Validator/ConfigurationValidator.cs @@ -7,9 +7,15 @@ namespace Unity.Services.Cli.Common.Validator; public class ConfigurationValidator : IConfigurationValidator { const string k_EnvironmentNameRegexPattern = "^[a-z0-9_-]*$"; + const string k_BucketNameRegexPattern = "^\\s*[^ \\s]+.*$"; const string k_GuidRegexPattern = "^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$"; - public const string EnvironmentNameInvalidMessage = "Valid input should have only lowercase alphanumerical and dash (-) characters."; + public const string EnvironmentNameInvalidMessage = + "Valid input should have only lowercase alphanumerical and dash (-) characters."; + + public const string BucketNameInvalidMessage = + "Valid input should have at least one character."; + public const string GuidInvalidMessage = "Valid input should have characters 0-9, a-f, A-F and follow the format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX."; @@ -39,8 +45,8 @@ public bool IsConfigValid(string key, string? value, out string errorMessage) return IsEnvironmentNameValid(value, out errorMessage); case Keys.ConfigKeys.ProjectId: return IsProjectIdValid(value, out errorMessage); - case Keys.ConfigKeys.BucketId: - return IsProjectIdValid(value, out errorMessage); + case Keys.ConfigKeys.BucketName: + return IsBucketNameValid(value, out errorMessage); default: errorMessage = InvalidKeyMsg; return false; @@ -68,10 +74,7 @@ public bool IsKeyValid(string key, out string errorMessage) public void ThrowExceptionIfConfigInvalid(string key, string value) { - if (!IsConfigValid(key, value, out var envError)) - { - throw new ConfigValidationException(key, value, envError); - } + if (!IsConfigValid(key, value, out var envError)) throw new ConfigValidationException(key, value, envError); } static bool IsEnvironmentIdValid(string value, out string errorMessage) @@ -112,4 +115,17 @@ static bool IsProjectIdValid(string value, out string errorMessage) errorMessage = ""; return true; } + + static bool IsBucketNameValid(string value, out string errorMessage) + { + var nameRegex = new Regex(k_BucketNameRegexPattern, RegexOptions.None, TimeSpan.FromSeconds(1)); + if (!nameRegex.IsMatch(value)) + { + errorMessage = BucketNameInvalidMessage; + return false; + } + + errorMessage = ""; + return true; + } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Console/CliPrompt.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/CliPrompt.cs deleted file mode 100644 index f095fdd..0000000 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/Console/CliPrompt.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Spectre.Console; - -namespace Unity.Services.Cli.Common.Console; - -class CliPrompt : ICliPrompt -{ - readonly IAnsiConsole m_Console; - public bool IsStandardInputRedirected { get; } - - public CliPrompt(IAnsiConsole console, bool isStandardInputRedirected) - { - m_Console = console; - IsStandardInputRedirected = isStandardInputRedirected; - } - - public Task PromptAsync(IPrompt prompt, CancellationToken cancellationToken) - => prompt.ShowAsync(m_Console, cancellationToken); -} diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ConsolePrompt.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ConsolePrompt.cs new file mode 100644 index 0000000..eb80d98 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ConsolePrompt.cs @@ -0,0 +1,65 @@ +using Spectre.Console; + +namespace Unity.Services.Cli.Common.Console; + +class ConsolePrompt : IConsolePrompt +{ + readonly IAnsiConsole m_Console; + public bool InteractiveEnabled { get; } + const int k_PageSize = 10; + + public ConsolePrompt(IAnsiConsole console, bool isStandardInputRedirected) + { + m_Console = console; + InteractiveEnabled = !isStandardInputRedirected; + } + + public Task PromptAsync(IPrompt prompt, CancellationToken cancellationToken) + => prompt.ShowAsync(m_Console, cancellationToken); + + public Task PromptAsync( + string title, + CancellationToken cancellationToken) + { + var prompt = new TextPrompt(title); + + return prompt.ShowAsync(m_Console, cancellationToken); + } + + public Task SelectionPromptAsync( + string title, + ICollection choices, + CancellationToken cancellationToken, + int pageSize = k_PageSize) + where T : notnull + { + var prompt = new SelectionPrompt() + .Title(title) + .PageSize(pageSize) + .AddChoices(choices); + + return prompt.ShowAsync(m_Console, cancellationToken); + } + + public Task> MultiSelectionPromptAsync( + string title, + ICollection choices, + CancellationToken cancellationToken, + int pageSize = k_PageSize) + where T : notnull + { + var prompt = new MultiSelectionPrompt() + .Title(title) + .PageSize(pageSize) + .AddChoices(choices); + + return prompt.ShowAsync(m_Console, cancellationToken); + } + + public Task ConfirmPromptAsync(string title, CancellationToken cancellationToken) + { + var prompt = new ConfirmationPrompt(title); + + return prompt.ShowAsync(m_Console, cancellationToken); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ConsoleTable.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ConsoleTable.cs new file mode 100644 index 0000000..a398316 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ConsoleTable.cs @@ -0,0 +1,59 @@ +using Spectre.Console; + +namespace Unity.Services.Cli.Common.Console; + +class ConsoleTable : IConsoleTable +{ + readonly IAnsiConsole m_Console; + public bool IsStandardInputRedirected { get; } + internal readonly Table SpectreTable = new(); + readonly bool m_OutputIsJson; + + public ConsoleTable( + IAnsiConsole console, + bool isStandardInputRedirected, + bool outputIsJson) + { + m_Console = console; + IsStandardInputRedirected = isStandardInputRedirected; + m_OutputIsJson = outputIsJson; + + // Define generic CLI Table style here + SpectreTable.Border(TableBorder.Rounded); + } + + public void DrawTable() + { + if (m_OutputIsJson) + { + return; + } + m_Console.Write(SpectreTable); + } + + public void AddColumn(Text title) + => SpectreTable.AddColumn(new TableColumn(title)); + + public void AddColumns(params Text[] titles) + { + foreach (var title in titles) + { + AddColumn(title); + } + } + + public void AddRow(params Text[] items) + => SpectreTable.AddRow(new TableRow(items)); + + public void RemoveRow(int index) + => SpectreTable.RemoveRow(index); + + public IReadOnlyList GetColumns() + => SpectreTable.Columns; + + public IReadOnlyList GetRows() + => SpectreTable.Rows; + + public void SetTitle(TableTitle title) + => SpectreTable.Title = title; +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ICliPrompt.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/IConsolePrompt.cs similarity index 56% rename from Unity.Services.Cli/Unity.Services.Cli.Common/Console/ICliPrompt.cs rename to Unity.Services.Cli/Unity.Services.Cli.Common/Console/IConsolePrompt.cs index 37fac72..1f53817 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ICliPrompt.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/IConsolePrompt.cs @@ -5,13 +5,13 @@ namespace Unity.Services.Cli.Common.Console; /// /// Cli Prompt to wrap IPrompt in Spectre Console /// -public interface ICliPrompt +public interface IConsolePrompt { /// /// Is the standard input redirected. /// Should default to System.Console.IsInputRedirected. /// - bool IsStandardInputRedirected { get; } + bool InteractiveEnabled { get; } /// /// Execute expected prompt and return user input for the prompt. @@ -29,4 +29,24 @@ public interface ICliPrompt /// Return the value from user. /// Task PromptAsync(IPrompt prompt, CancellationToken cancellationToken); + + Task PromptAsync( + string title, + CancellationToken cancellationToken); + + Task SelectionPromptAsync( + string title, + ICollection choices, + CancellationToken cancellationToken, + int pageSize) + where T : notnull; + + Task> MultiSelectionPromptAsync( + string title, + ICollection choices, + CancellationToken cancellationToken, + int pageSize) + where T : notnull; + + Task ConfirmPromptAsync(string title, CancellationToken cancellationToken); } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Console/IConsoleTable.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/IConsoleTable.cs new file mode 100644 index 0000000..34b9201 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/IConsoleTable.cs @@ -0,0 +1,15 @@ +using Spectre.Console; + +namespace Unity.Services.Cli.Common.Console; + +public interface IConsoleTable +{ + void DrawTable(); + void AddColumn(Text title); + void AddColumns(params Text[] titles); + void AddRow(params Text[] items); + void RemoveRow(int index); + IReadOnlyList GetColumns(); + IReadOnlyList GetRows(); + void SetTitle(TableTitle title); +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ILoadingIndicator.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ILoadingIndicator.cs index 47ea4ba..293302f 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ILoadingIndicator.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ILoadingIndicator.cs @@ -10,5 +10,5 @@ public interface ILoadingIndicator /// Description of the loading indicator /// The callback that the status will be tracking /// - public Task StartLoadingAsync(string description, Func callback); + Task StartLoadingAsync(string description, Func callback); } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Console/IProgressBar.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/IProgressBar.cs index 421ad2d..eeea5de 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/Console/IProgressBar.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/IProgressBar.cs @@ -9,5 +9,5 @@ public interface IProgressBar /// /// The callback that the progress bars will be tracking /// - public Task StartProgressAsync(Func callback); + Task StartProgressAsync(Func callback); } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Console/LoadingIndicator.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/LoadingIndicator.cs index 73a818a..5cc6d53 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/Console/LoadingIndicator.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/LoadingIndicator.cs @@ -4,16 +4,16 @@ namespace Unity.Services.Cli.Common.Console; public class LoadingIndicator : ILoadingIndicator { - internal readonly IAnsiConsole? k_AnsiConsole; + internal readonly IAnsiConsole? AnsiConsole; public LoadingIndicator(IAnsiConsole? console) { - k_AnsiConsole = console; + AnsiConsole = console; } public async Task StartLoadingAsync(string description, Func callback) { - await (k_AnsiConsole is null ? callback(null) : StartAnsiConsoleStatusAsync(k_AnsiConsole, description, callback)); + await (AnsiConsole is null ? callback(null) : StartAnsiConsoleStatusAsync(AnsiConsole, description, callback)); } static async Task StartAnsiConsoleStatusAsync(IAnsiConsole console, string description, Func callback) => diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ProgressBar.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ProgressBar.cs index 2c8c493..0258bed 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ProgressBar.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Console/ProgressBar.cs @@ -4,9 +4,9 @@ namespace Unity.Services.Cli.Common.Console; public class ProgressBar : IProgressBar { - internal readonly IAnsiConsole? k_AnsiConsole; + internal readonly IAnsiConsole? AnsiConsole; - readonly ProgressColumn[] k_ProgressColumns = + readonly ProgressColumn[] m_ProgressColumns = { new TaskDescriptionColumn(), new ProgressBarColumn(), @@ -16,14 +16,14 @@ public class ProgressBar : IProgressBar public ProgressBar(IAnsiConsole? console) { - k_AnsiConsole = console; + AnsiConsole = console; } public async Task StartProgressAsync(Func callback) { - await (k_AnsiConsole is null ? callback(null) : StartAnsiConsoleProgressAsync(k_AnsiConsole, callback)); + await (AnsiConsole is null ? callback(null) : StartAnsiConsoleProgressAsync(AnsiConsole, callback)); } async Task StartAnsiConsoleProgressAsync(IAnsiConsole console, Func callback) => - await console.Progress().Columns(k_ProgressColumns).StartAsync(callback); + await console.Progress().Columns(m_ProgressColumns).StartAsync(callback); } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Exceptions/CliException.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Exceptions/CliException.cs index 031ef6b..c762cd7 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/Exceptions/CliException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Exceptions/CliException.cs @@ -5,13 +5,10 @@ namespace Unity.Services.Cli.Common.Exceptions; /// /// Exception caused by user operation. The exception message should include instructions to fix operation /// -[Serializable] public class CliException : Exception { public int ExitCode { get; } - protected CliException(SerializationInfo info, StreamingContext context) : base(info, context) { } - public CliException(string message, Exception? innerException, int exitCode) : base(message, innerException) { diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Exceptions/DeploymentFailureException.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Exceptions/DeploymentFailureException.cs index 40aff47..5244ced 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/Exceptions/DeploymentFailureException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Exceptions/DeploymentFailureException.cs @@ -5,13 +5,10 @@ namespace Unity.Services.Cli.Common.Exceptions; /// /// Exception caused by a failure in the deployment process /// -[Serializable] public class DeploymentFailureException : Exception { public int ExitCode { get; } - protected DeploymentFailureException(SerializationInfo info, StreamingContext context) : base(info, context) { } - public DeploymentFailureException() : base("", null) { diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Process/CliProcess.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Process/CliProcess.cs index fe252a4..9c15240 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/Process/CliProcess.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Process/CliProcess.cs @@ -38,9 +38,9 @@ public async Task ExecuteAsync(string executablePath, string workingDire return output; } - catch (Win32Exception) + catch (Win32Exception e) { - throw new ProcessException($"Can not find {process.StartInfo.FileName}, please ensure its path is configured in PATH environment variable"); + throw new ProcessException($"Can not find {process.StartInfo.FileName}, please ensure its path is configured in PATH environment variable", e, Exceptions.ExitCode.HandledError); } } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Process/ProcessException.cs b/Unity.Services.Cli/Unity.Services.Cli.Common/Process/ProcessException.cs index 3c22ae4..05e47ad 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/Process/ProcessException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Process/ProcessException.cs @@ -3,13 +3,8 @@ namespace Unity.Services.Cli.Common.Process; -[Serializable] public class ProcessException : CliException { - protected ProcessException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - public ProcessException(string message, Exception? innerException, int exitCode) : base(message, innerException, exitCode) { } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Common/Unity.Services.Cli.Common.csproj b/Unity.Services.Cli/Unity.Services.Cli.Common/Unity.Services.Cli.Common.csproj index 837e43e..18730ec 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Common/Unity.Services.Cli.Common.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Common/Unity.Services.Cli.Common.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable enable true @@ -10,7 +10,7 @@ - + diff --git a/Unity.Services.Cli/Unity.Services.Cli.Economy.UnitTest/Unity.Services.Cli.Economy.UnitTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.Economy.UnitTest/Unity.Services.Cli.Economy.UnitTest.csproj index d7ca775..c86514c 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Economy.UnitTest/Unity.Services.Cli.Economy.UnitTest.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Economy.UnitTest/Unity.Services.Cli.Economy.UnitTest.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable false true diff --git a/Unity.Services.Cli/Unity.Services.Cli.Economy/Exceptions/InvalidResourceException.cs b/Unity.Services.Cli/Unity.Services.Cli.Economy/Exceptions/InvalidResourceException.cs index c1b1d9d..ce4f87e 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Economy/Exceptions/InvalidResourceException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Economy/Exceptions/InvalidResourceException.cs @@ -2,7 +2,6 @@ namespace Unity.Services.Cli.Economy.Exceptions; -[Serializable] public class InvalidResourceException : ApiException { public InvalidResourceException(string message, Exception innerException) diff --git a/Unity.Services.Cli/Unity.Services.Cli.Economy/Unity.Services.Cli.Economy.csproj b/Unity.Services.Cli/Unity.Services.Cli.Economy/Unity.Services.Cli.Economy.csproj index 8b45cf4..7f5ade7 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Economy/Unity.Services.Cli.Economy.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Economy/Unity.Services.Cli.Economy.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 10 enable enable @@ -25,7 +25,7 @@ - + diff --git a/Unity.Services.Cli/Unity.Services.Cli.Environment.UnitTest/Handlers/ListHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Environment.UnitTest/Handlers/ListHandlerTests.cs index 093731c..0cbd02a 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Environment.UnitTest/Handlers/ListHandlerTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Environment.UnitTest/Handlers/ListHandlerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -30,6 +31,13 @@ public void SetUp() m_MockHelper.MockConfiguration .Setup(c => c.GetConfigArgumentsAsync(Models.Keys.ConfigKeys.ProjectId, CancellationToken.None)) .Returns(Task.FromResult(k_ValidProjectId)!); + m_MockHelper.MockConsoleTable.Setup(t => t.GetColumns()) + .Returns( + new List() + { + new(""), + new("") + }); } [Test] @@ -41,11 +49,16 @@ public async Task LoadListAsync_CallsLoadingIndicatorStartLoading() CloudProjectId = null }; - await ListHandler.ListAsync(input, m_MockHelper.MockEnvironment.Object, m_MockHelper.MockLogger.Object, - mockLoadingIndicator.Object, CancellationToken.None); + await ListHandler.ListAsync(input, + m_MockHelper.MockEnvironment.Object, + m_MockHelper.MockConfiguration.Object, + m_MockHelper.MockConsoleTable.Object, + m_MockHelper.MockLogger.Object, + mockLoadingIndicator.Object, + CancellationToken.None); mockLoadingIndicator.Verify(ex => ex - .StartLoadingAsync(It.IsAny(), It.IsAny>()), Times.Once); + .StartLoadingAsync(It.IsAny(), It.IsAny>()), Times.Once); } [Test] @@ -60,6 +73,8 @@ public void ListAsync_NullProjectIdPrintsError() ListHandler.ListAsync( input, m_MockHelper.MockEnvironment.Object, + m_MockHelper.MockConfiguration.Object, + m_MockHelper.MockConsoleTable.Object, m_MockHelper.MockLogger.Object, CancellationToken.None )); @@ -76,20 +91,45 @@ public void ListAsync_NullProjectIdPrintsError() } [TestCase(k_ValidProjectId)] - public async Task ListAsync_ProjectIdOptionDoNotRunConfiguration(string projectId) + public async Task ListAsync_ProjectIdOptionRunsConfiguration(string projectId) { var input = new EnvironmentInput { CloudProjectId = projectId }; - await ListHandler.ListAsync(input, - m_MockHelper.MockEnvironment.Object, m_MockHelper.MockLogger.Object, CancellationToken.None); + await ListHandler.ListAsync( + input, + m_MockHelper.MockEnvironment.Object, + m_MockHelper.MockConfiguration.Object, + m_MockHelper.MockConsoleTable.Object, + m_MockHelper.MockLogger.Object, + CancellationToken.None); + + Assert.AreEqual(1, m_MockHelper.MockConfiguration.Invocations.Count); - Assert.AreEqual(0, m_MockHelper.MockConfiguration.Invocations.Count); m_MockHelper.MockEnvironment.Verify(e => e.ListAsync(input.CloudProjectId, CancellationToken.None)); - TestsHelper.VerifyLoggerWasCalled(m_MockHelper.MockLogger, LogLevel.Critical, LoggerExtension.ResultEventId); + } + + [Test] + public async Task ListAsync_CallsDrawTable() + { + Mock mockLoadingIndicator = new Mock(); + var input = new EnvironmentInput + { + CloudProjectId = null + }; + + await ListHandler.ListAsync(input, + m_MockHelper.MockEnvironment.Object, + m_MockHelper.MockConfiguration.Object, + m_MockHelper.MockConsoleTable.Object, + m_MockHelper.MockLogger.Object, + mockLoadingIndicator.Object, + CancellationToken.None); + + m_MockHelper.MockConsoleTable.Verify(t => t.DrawTable(), Times.Once); } [Test] @@ -107,10 +147,80 @@ public async Task ListAsync_JsonOptionLogsResultInCorrectFormat() .ListAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(response); - await ListHandler.ListAsync(input, - m_MockHelper.MockEnvironment.Object, m_MockHelper.MockLogger.Object, CancellationToken.None); + await ListHandler.ListAsync( + input, + m_MockHelper.MockEnvironment.Object, + m_MockHelper.MockConfiguration.Object, + m_MockHelper.MockConsoleTable.Object, + m_MockHelper.MockLogger.Object, + CancellationToken.None); TestsHelper.VerifyLoggerWasCalled(m_MockHelper.MockLogger, LogLevel.Critical, LoggerExtension.ResultEventId, Times.Once, response.ToString()); } + + [Test] + public async Task ListAsync_TableOutputNullRowIfNoEnvironments() + { + var input = new EnvironmentInput + { + CloudProjectId = k_ValidProjectId + }; + + m_MockHelper.MockEnvironment.Setup(ex => ex + .ListAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new List()); + + await ListHandler.ListAsync( + input, + m_MockHelper.MockEnvironment.Object, + m_MockHelper.MockConfiguration.Object, + m_MockHelper.MockConsoleTable.Object, + m_MockHelper.MockLogger.Object, + CancellationToken.None); + + var nullText = new Text("Ø"); + + m_MockHelper.MockConsoleTable.Verify(t => t.AddRow( + It.Is(text => text.ToString() == nullText.ToString()), + It.Is(text => text.ToString() == nullText.ToString())), + Times.Once); + } + + [Test] + public async Task ListAsync_TableOutputAddsRowForEachEnvironment() + { + var input = new EnvironmentInput + { + CloudProjectId = k_ValidProjectId + }; + + EnvironmentResponse[] response = + { + new() + { + Name = "env1", + Id = new Guid("00000000-0000-0000-0000-000000000001") + }, + new() + { + Name = "env2", + Id = new Guid("00000000-0000-0000-0000-000000000002") + } + }; + + m_MockHelper.MockEnvironment.Setup(ex => ex + .ListAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(response); + + await ListHandler.ListAsync( + input, + m_MockHelper.MockEnvironment.Object, + m_MockHelper.MockConfiguration.Object, + m_MockHelper.MockConsoleTable.Object, + m_MockHelper.MockLogger.Object, + CancellationToken.None); + + m_MockHelper.MockConsoleTable.Verify(t => t.AddRow(It.IsAny()), Times.Exactly(2)); + } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Environment.UnitTest/Unity.Services.Cli.Environment.UnitTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.Environment.UnitTest/Unity.Services.Cli.Environment.UnitTest.csproj index 72bd904..1d153c0 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Environment.UnitTest/Unity.Services.Cli.Environment.UnitTest.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Environment.UnitTest/Unity.Services.Cli.Environment.UnitTest.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable true false diff --git a/Unity.Services.Cli/Unity.Services.Cli.Environment/EnvironmentModule.cs b/Unity.Services.Cli/Unity.Services.Cli.Environment/EnvironmentModule.cs index fbbdc20..ee3806d 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Environment/EnvironmentModule.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Environment/EnvironmentModule.cs @@ -35,8 +35,15 @@ public EnvironmentModule() { CommonInput.CloudProjectIdOption }; - ListCommand.SetHandler( - ListHandler.ListAsync); + ListCommand.SetHandler< + EnvironmentInput, + IEnvironmentService, + IConfigurationService, + IConsoleTable, + ILogger, + ILoadingIndicator, + CancellationToken> + (ListHandler.ListAsync); AddCommand = new( "add", diff --git a/Unity.Services.Cli/Unity.Services.Cli.Environment/Handlers/ListHandler.cs b/Unity.Services.Cli/Unity.Services.Cli.Environment/Handlers/ListHandler.cs index 9d29b7e..74e28ff 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Environment/Handlers/ListHandler.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Environment/Handlers/ListHandler.cs @@ -1,24 +1,37 @@ -using System.Collections.Immutable; using Microsoft.Extensions.Logging; +using Spectre.Console; +using Unity.Services.Cli.Common; using Unity.Services.Cli.Common.Console; using Unity.Services.Cli.Common.Exceptions; using Unity.Services.Cli.Common.Logging; using Unity.Services.Cli.Common.Models; using Unity.Services.Cli.Environment.Input; +using Unity.Services.Gateway.IdentityApiV1.Generated.Model; namespace Unity.Services.Cli.Environment.Handlers; static class ListHandler { - public static async Task ListAsync(EnvironmentInput input, IEnvironmentService environmentService, ILogger logger, - ILoadingIndicator loadingIndicator, CancellationToken cancellationToken) + public static async Task ListAsync(EnvironmentInput input, + IEnvironmentService environmentService, + IConfigurationService configurationService, + IConsoleTable consoleTable, + ILogger logger, + ILoadingIndicator loadingIndicator, + CancellationToken cancellationToken) { - await loadingIndicator.StartLoadingAsync("Fetching environments...", _ => - ListAsync(input, environmentService, logger, cancellationToken)); + await loadingIndicator.StartLoadingAsync("Fetching environments...", _ => + ListAsync(input, environmentService, configurationService, consoleTable, logger, cancellationToken)); + + consoleTable.DrawTable(); } - internal static async Task ListAsync(EnvironmentInput input, IEnvironmentService environmentService, - ILogger logger, CancellationToken cancellationToken) + internal static async Task ListAsync(EnvironmentInput input, + IEnvironmentService environmentService, + IConfigurationService configurationService, + IConsoleTable table, + ILogger logger, + CancellationToken cancellationToken) { var projectId = input.CloudProjectId ?? throw new MissingConfigurationException( Keys.ConfigKeys.ProjectId, Keys.EnvironmentKeys.ProjectId); @@ -29,12 +42,45 @@ internal static async Task ListAsync(EnvironmentInput input, IEnvironmentService if (input.IsJson) { logger.LogResultValue(environments); + return; + } + + string? currentEnvironment = null; + + try + { + currentEnvironment = await configurationService.GetConfigArgumentsAsync( + Keys.ConfigKeys.EnvironmentName, + cancellationToken); + } + catch (MissingConfigurationException) + { + // Command should still run normally if no environment is set in configuration } - else + + FillTable(environments.ToList(), currentEnvironment, table); + } + + static void FillTable(List environmentList, string? currentEnvironment, IConsoleTable table) + { + table.AddColumns(new Text("Environment Name"), new Text("Environment ID")); + table.GetColumns()[1].NoWrap = true; + + environmentList.ForEach(e => + { + if (currentEnvironment != null && e.Name.Equals(currentEnvironment)) + { + table.AddRow(new Text(e.Name + " (in use)", new Style(Color.Green)), new Text(e.Id.ToString())); + } + else + { + table.AddRow(new Text(e.Name), new Text(e.Id.ToString())); + } + }); + + if (environmentList.Count == 0) { - var environmentNames = environments - .Select(e => $"\"{e.Name}\": \"{e.Id}\""); - logger.LogResultValue(environmentNames); + table.AddRow(new Text("Ø"), new Text("Ø")); } } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Environment/Unity.Services.Cli.Environment.csproj b/Unity.Services.Cli/Unity.Services.Cli.Environment/Unity.Services.Cli.Environment.csproj index 931e506..b68a6b1 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Environment/Unity.Services.Cli.Environment.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Environment/Unity.Services.Cli.Environment.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable enable true @@ -14,7 +14,7 @@ - + diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/BuildClientTests.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/BuildClientTests.cs index 739726d..4ddc33a 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/BuildClientTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/BuildClientTests.cs @@ -42,7 +42,8 @@ public async Task FindByName_WithOneResult_ReturnsId() 0, "test", buildVersionName: ValidBuildVersionName, - ccd: new CCDDetails()) + ccd: new CCDDetails(), + syncStatus: BuildListInner.SyncStatusEnum.SYNCED) }); var res = await m_Client!.FindByName("test"); diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/CcdCloudStorageTests.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/CcdCloudStorageTests.cs index 751ef06..0ff7f44 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/CcdCloudStorageTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/CcdCloudStorageTests.cs @@ -61,14 +61,16 @@ public async Task CreateBucket_CreatesNewBucket() [Test] public async Task UploadBuildEntries_UploadsNewEntries() { - SetupUpload(new List()); + SetupUpload(new List()); await m_CcdCloudStorage!.UploadBuildEntries( new CloudBucketId { Id = Guid.NewGuid() }, new List { new ("path", new MemoryStream(Encoding.UTF8.GetBytes("content"))) - }); + }, + onUpdated: _ => { } + ); m_MockEntriesApi!.Verify(api => api.CreateOrUpdateEntryByPathEnvAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); } @@ -76,7 +78,7 @@ public async Task UploadBuildEntries_UploadsNewEntries() [Test] public async Task UploadBuildEntries_WhenExactFileExists_DoesNotUpload() { - SetupUpload(new List + SetupUpload(new List { // Uppercase hash to ensure that case differences are handled new (path: "path", contentHash: "9a0364b9e99bb480dd25e1f0284c8555".ToUpperInvariant()) @@ -87,7 +89,8 @@ public async Task UploadBuildEntries_WhenExactFileExists_DoesNotUpload() new List { new ("path", new MemoryStream(Encoding.UTF8.GetBytes("content"))) - }); + }, + onUpdated: _ => { }); m_MockEntriesApi!.Verify(api => api.CreateOrUpdateEntryByPathEnvAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); } @@ -95,24 +98,25 @@ public async Task UploadBuildEntries_WhenExactFileExists_DoesNotUpload() [Test] public async Task UploadBuildEntries_WhenOrphanExists_DeletesOrphan() { - SetupUpload(new List + SetupUpload(new List { new (path: "path", contentHash: "hash") }); await m_CcdCloudStorage!.UploadBuildEntries( new CloudBucketId { Id = Guid.NewGuid() }, - new List()); + new List(), + onUpdated: _ => { }); m_MockEntriesApi!.Verify(api => api.DeleteEntryEnvAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); } - void SetupUpload(List ccdEntries) + void SetupUpload(List ccdEntries) { m_MockEntriesApi!.Setup(api => api.GetEntriesEnvAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(ccdEntries)); m_MockEntriesApi.Setup(api => api.CreateOrUpdateEntryByPathEnvAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(Task.FromResult(new CcdGetEntries200ResponseInner(signedUrl: "https://signed.url.example.com"))); + .Returns(Task.FromResult(new CcdCreateOrUpdateEntryBatch200ResponseInner(signedUrl: "https://signed.url.example.com"))); m_MockEntriesApi.Setup(api => api.DeleteEntryEnvAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(new List())); m_MockMessageHandler.Protected() diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/DummyBinaryBuilderTests.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/DummyBinaryBuilderTests.cs index 261a33c..1bd3e9e 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/DummyBinaryBuilderTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/DummyBinaryBuilderTests.cs @@ -18,7 +18,7 @@ public void RevertToOriginalBuildTarget_DoesNothing() { Assert.DoesNotThrow(() => { - new DummyBinaryBuilder().RevertToOriginalBuildTarget(); + new DummyBinaryBuilder().WarnBuildTargetChanged(); }); } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/FleetsClientTests.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/FleetsClientTests.cs index d4c82b6..ff1ce0f 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/FleetsClientTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Services/FleetsClientTests.cs @@ -5,6 +5,7 @@ using Unity.Services.Gateway.GameServerHostingApiV1.Generated.Model; using Unity.Services.Multiplay.Authoring.Core.Assets; using Unity.Services.Multiplay.Authoring.Core.MultiplayApi; +using ServerStatus = Unity.Services.Gateway.GameServerHostingApiV1.Generated.Model.ServerStatus; namespace Unity.Services.Cli.GameServerHosting.UnitTest.Services; @@ -59,7 +60,7 @@ public async Task FindByName_WithOneResult_ReturnsId() all: new FleetServerBreakdown(new ServerStatus()), cloud: new FleetServerBreakdown(new ServerStatus()), metal: new FleetServerBreakdown(new ServerStatus()) - ) + ), status: FleetListItem.StatusEnum.ONLINE ) }); @@ -68,50 +69,6 @@ public async Task FindByName_WithOneResult_ReturnsId() Assert.That(res, Is.Not.Null); } - [Test] - public void FindByName_WithMultipleResults_ThrowsDuplicateException() - { - m_MockApi!.Setup(a => - a.ListFleetsAsync( - Guid.Empty, - Guid.Empty, - default, - default)) - .ReturnsAsync(new List - { - new FleetListItem( - allocationType: FleetListItem.AllocationTypeEnum.ALLOCATION, - new List(), - graceful: false, - Guid.Empty, - name: "test", - osName: string.Empty, - regions: new List(), - servers: new Servers( - all: new FleetServerBreakdown(new ServerStatus()), - cloud: new FleetServerBreakdown(new ServerStatus()), - metal: new FleetServerBreakdown(new ServerStatus()) - ) - ), - new FleetListItem( - allocationType: FleetListItem.AllocationTypeEnum.ALLOCATION, - new List(), - graceful: false, - Guid.Empty, - name: "test", - osName: string.Empty, - regions: new List(), - servers: new Servers( - all: new FleetServerBreakdown(new ServerStatus()), - cloud: new FleetServerBreakdown(new ServerStatus()), - metal: new FleetServerBreakdown(new ServerStatus()) - ) - ) - }); - - Assert.ThrowsAsync(async () => await m_Client!.FindByName("test")); - } - [Test] public async Task Create_CallsCreateApi() { @@ -130,9 +87,10 @@ public async Task Create_CallsCreateApi() all: new FleetServerBreakdown(new ServerStatus()), cloud: new FleetServerBreakdown(new ServerStatus()), metal: new FleetServerBreakdown(new ServerStatus()) - ))); + ), + status: Fleet.StatusEnum.ONLINE)); - await m_Client!.Create("test", new List(), new MultiplayConfig.FleetDefinition()); + await m_Client!.Create("test", new List(), new MultiplayConfig.FleetDefinition(), CancellationToken.None); m_MockApi.Verify(a => a.CreateFleetAsync(Guid.Empty, Guid.Empty, null, It.IsAny(), default, default)); @@ -158,7 +116,7 @@ public async Task Update_CallsTheUpdateApi() metal: new FleetServerBreakdown(new ServerStatus()) ))); - await m_Client!.Update(new FleetId(), "test", new List(), new MultiplayConfig.FleetDefinition()); + await m_Client!.Update(new FleetId(), "test", new List(), new MultiplayConfig.FleetDefinition(), Guid.Empty); m_MockApi.Verify(a => a.UpdateFleetAsync(Guid.Empty, Guid.Empty, Guid.Empty, It.IsAny(), default, default)); @@ -188,7 +146,7 @@ public async Task Update_RemovesOldFleets() metal: new FleetServerBreakdown(new ServerStatus()) ))); - await m_Client!.Update(new FleetId(), "test", new List(), new MultiplayConfig.FleetDefinition()); + await m_Client!.Update(new FleetId(), "test", new List(), new MultiplayConfig.FleetDefinition(), Guid.Empty); m_MockApi.Verify(f => f.UpdateFleetRegionAsync(Guid.Empty, Guid.Empty, Guid.Empty, regionId, null, default, default)); } @@ -223,7 +181,7 @@ public async Task Update_AddsNewFleets() { {"North-America", new MultiplayConfig.ScalingDefinition()} } - }); + }, Guid.Empty); m_MockApi.Verify(f => f.AddFleetRegionAsync(Guid.Empty, Guid.Empty, Guid.Empty, null, It.IsAny(), default, default)); @@ -262,7 +220,7 @@ public async Task Update_UpdatesExistingFleets() { {"North-America", new MultiplayConfig.ScalingDefinition()} } - }); + }, Guid.Empty); m_MockApi.Verify(f => f.UpdateFleetRegionAsync(Guid.Empty, Guid.Empty, Guid.Empty, regionId, It.IsAny(), default, default)); } diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Unity.Services.Cli.GameServerHosting.UnitTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Unity.Services.Cli.GameServerHosting.UnitTest.csproj index 9b6e004..6aadda0 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Unity.Services.Cli.GameServerHosting.UnitTest.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting.UnitTest/Unity.Services.Cli.GameServerHosting.UnitTest.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable enable false diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/DuplicateResourceException.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/DuplicateResourceException.cs index aa087c8..d040d05 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/DuplicateResourceException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/DuplicateResourceException.cs @@ -3,13 +3,10 @@ namespace Unity.Services.Cli.GameServerHosting.Exceptions; -[Serializable] public class DuplicateResourceException : CliException { public DuplicateResourceException(string resource, string name) : base($"found duplicate {resource} of name {name}", Common.Exceptions.ExitCode.HandledError) { } - - protected DuplicateResourceException(SerializationInfo info, StreamingContext context) : base(info, context) { } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidConfigException.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidConfigException.cs index b31fee6..69f76a0 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidConfigException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidConfigException.cs @@ -3,11 +3,8 @@ namespace Unity.Services.Cli.GameServerHosting.Exceptions; -[Serializable] public class InvalidConfigException : CliException { public InvalidConfigException(string path) : base($"Game Server Hosting Config file is invalid. See output for details: {path}", Common.Exceptions.ExitCode.HandledError) { } - - protected InvalidConfigException(SerializationInfo info, StreamingContext context) : base(info, context) { } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidExtensionException.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidExtensionException.cs index 1728a63..5e0ebdf 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidExtensionException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidExtensionException.cs @@ -3,11 +3,8 @@ namespace Unity.Services.Cli.GameServerHosting.Exceptions; -[Serializable] public class InvalidExtensionException : CliException { public InvalidExtensionException(string path, string extension) : base($"File path must end in '{extension}': {path}", Common.Exceptions.ExitCode.HandledError) { } - - protected InvalidExtensionException(SerializationInfo info, StreamingContext context) : base(info, context) { } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidIdsListException.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidIdsListException.cs index c692c65..b798044 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidIdsListException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidIdsListException.cs @@ -3,10 +3,8 @@ namespace Unity.Services.Cli.GameServerHosting.Exceptions; -[Serializable] public class InvalidDateRangeException : CliException { - protected InvalidDateRangeException(SerializationInfo info, StreamingContext context) : base(info, context) { } public InvalidDateRangeException(string input) : base($"Invalid date range: '{input}'", Common.Exceptions.ExitCode.HandledError) { } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidKeyValuePairException.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidKeyValuePairException.cs index d5e08c6..ec70d8a 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidKeyValuePairException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidKeyValuePairException.cs @@ -3,10 +3,8 @@ namespace Unity.Services.Cli.GameServerHosting.Exceptions; -[Serializable] public class InvalidKeyValuePairException : CliException { - protected InvalidKeyValuePairException(SerializationInfo info, StreamingContext context) : base(info, context) { } public InvalidKeyValuePairException(string input) : base($"Could not parse key:value pair from input: '{input}'", Common.Exceptions.ExitCode.HandledError) { } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidResponseException.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidResponseException.cs index 26d120f..eaceb65 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidResponseException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/InvalidResponseException.cs @@ -3,10 +3,8 @@ namespace Unity.Services.Cli.GameServerHosting.Exceptions; -[Serializable] public class InvalidResponseException : CliException { - protected InvalidResponseException(SerializationInfo info, StreamingContext context) : base(info, context) { } public InvalidResponseException(string reason) : base($"Response is invalid: '{reason}'", Common.Exceptions.ExitCode.HandledError) { } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/MissingInputException.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/MissingInputException.cs index 0ded253..7e3c561 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/MissingInputException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/MissingInputException.cs @@ -3,10 +3,8 @@ namespace Unity.Services.Cli.GameServerHosting.Exceptions; -[Serializable] public class MissingInputException : CliException { - protected MissingInputException(SerializationInfo info, StreamingContext context) : base(info, context) { } public MissingInputException(string input) : base($"Missing value for input: '{input}'", Common.Exceptions.ExitCode.HandledError) { } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/PathNotFoundException.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/PathNotFoundException.cs index a9a425a..8dcf4d2 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/PathNotFoundException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/PathNotFoundException.cs @@ -3,11 +3,8 @@ namespace Unity.Services.Cli.GameServerHosting.Exceptions; -[Serializable] public class PathNotFoundException : CliException { public PathNotFoundException(string path) : base($"File path {path} could not be found.", Common.Exceptions.ExitCode.HandledError) { } - - protected PathNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/SyncFailedException.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/SyncFailedException.cs index 7472bc4..c9dba60 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/SyncFailedException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Exceptions/SyncFailedException.cs @@ -3,13 +3,10 @@ namespace Unity.Services.Cli.GameServerHosting.Exceptions; -[Serializable] public class SyncFailedException : CliException { public SyncFailedException() : base("failed to sync build", Common.Exceptions.ExitCode.HandledError) { } - - protected SyncFailedException(SerializationInfo info, StreamingContext context) : base(info, context) { } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/GameServerHostingModule.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/GameServerHostingModule.cs index 6d8de06..71af090 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/GameServerHostingModule.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/GameServerHostingModule.cs @@ -26,6 +26,7 @@ using CloudContentDeliveryConfiguration = Unity.Services.Gateway.ContentDeliveryManagementApiV1.Generated.Client.Configuration; using IBuildsApi = Unity.Services.Gateway.GameServerHostingApiV1.Generated.Api.IBuildsApi; +using IServersApi = Unity.Services.Gateway.GameServerHostingApiV1.Generated.Api.IServersApi; namespace Unity.Services.Cli.GameServerHosting; diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Services/CcdCloudStorageClient.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Services/CcdCloudStorageClient.cs index 0979aaa..e453289 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Services/CcdCloudStorageClient.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Services/CcdCloudStorageClient.cs @@ -39,7 +39,11 @@ public async Task CreateBucket(string name, CancellationToken can return new CloudBucketId { Id = res.Id }; } - public async Task UploadBuildEntries(CloudBucketId bucket, IList localEntries, CancellationToken cancellationToken = default) + public async Task UploadBuildEntries( + CloudBucketId bucket, + IList localEntries, + Action onUpdated, + CancellationToken cancellationToken = default) { var changes = 0; var remoteEntries = await ListAllRemoteEntries(bucket, cancellationToken); @@ -74,19 +78,19 @@ await orphans.BatchAsync(k_BatchSize, async orphan => return changes; } - async Task DeleteEntry(CloudBucketId bucket, CcdGetEntries200ResponseInner entry, CancellationToken cancellationToken = default) + async Task DeleteEntry(CloudBucketId bucket, CcdCreateOrUpdateEntryBatch200ResponseInner entry, CancellationToken cancellationToken = default) { await m_EntriesApiClient.DeleteEntryEnvAsync(m_ApiConfig.EnvironmentId.ToString(), bucket.ToString(), entry.Entryid.ToString(), m_ApiConfig.ProjectId.ToString(), cancellationToken: cancellationToken); } - async Task CreateOrUpdateEntry(CloudBucketId bucket, string path, string hash, int length, CancellationToken cancellationToken = default) + async Task CreateOrUpdateEntry(CloudBucketId bucket, string path, string hash, int length, CancellationToken cancellationToken = default) { var create = new CcdCreateOrUpdateEntryByPathRequest(hash, length, signedUrl: true); var res = await m_EntriesApiClient.CreateOrUpdateEntryByPathEnvAsync(m_ApiConfig.EnvironmentId.ToString(), bucket.ToString(), path, m_ApiConfig.ProjectId.ToString(), create, updateIfExists: true, cancellationToken: cancellationToken); return res; } - async Task UploadSignedContent(CcdGetEntries200ResponseInner entry, Stream content, CancellationToken cancellationToken = default) + async Task UploadSignedContent(CcdCreateOrUpdateEntryBatch200ResponseInner entry, Stream content, CancellationToken cancellationToken = default) { // Signed uploads need to be done using HTTP Client // Unity generated client does not support sending application/offset+octet-stream @@ -97,12 +101,12 @@ async Task UploadSignedContent(CcdGetEntries200ResponseInner entry, Stream conte res.EnsureSuccessStatusCode(); } - async Task> ListAllRemoteEntries(CloudBucketId bucket, CancellationToken cancellationToken = default) + async Task> ListAllRemoteEntries(CloudBucketId bucket, CancellationToken cancellationToken = default) { const int entriesPerPage = 100; - var entries = new Dictionary(); + var entries = new Dictionary(); - List res; + List res; var page = 1; do { diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Services/DummyBinaryBuilder.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Services/DummyBinaryBuilder.cs index 892c420..f3e27a9 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Services/DummyBinaryBuilder.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Services/DummyBinaryBuilder.cs @@ -10,8 +10,5 @@ public ServerBuild BuildLinuxServer(string outDir, string executable) return new ServerBuild(Path.Combine(outDir, executable)); } - public void RevertToOriginalBuildTarget() - { - - } + public void WarnBuildTargetChanged() { } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Services/FleetClient.cs b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Services/FleetClient.cs index 4591435..7ec239e 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Services/FleetClient.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Services/FleetClient.cs @@ -17,22 +17,86 @@ public FleetClient(IFleetsApiAsync fleetsApiAsync, GameServerHostingApiConfig ap m_ApiConfig = apiConfig; } - public async Task FindByName(string name, CancellationToken cancellationToken = default) + public async Task FindByName(string name, CancellationToken cancellationToken = default) { - var res = await m_FleetsApiAsync.ListFleetsAsync(m_ApiConfig.ProjectId, m_ApiConfig.EnvironmentId, cancellationToken: cancellationToken); - var filtered = res.Where(f => f.Name == name).ToList(); - switch (filtered.Count(b => b.Name == name)) + var response = await m_FleetsApiAsync.ListFleetsAsync(m_ApiConfig.ProjectId, m_ApiConfig.EnvironmentId, cancellationToken: cancellationToken); + var result = response.FirstOrDefault( + b => b.Name == name); + + if (result == null) + return null; + + return new FleetInfo( + result.Name, + new FleetId + { Id = result.Id} , + FromApi(result.Status, name), + #pragma warning disable 0612 //ignore obsolete API warning. We actually need it later. + result.OsID, + result.OsName, + FromApi(result.Regions)); + } + + static FleetInfo.Status FromApi(FleetListItem.StatusEnum statusOption, string fleetName) + { + switch (statusOption) { - case 0: - return null; - case 1: - return new FleetId { Id = filtered[0].Id }; + case FleetListItem.StatusEnum.ONLINE: + return FleetInfo.Status.Online; + case FleetListItem.StatusEnum.DRAINING: + return FleetInfo.Status.Draining; + case FleetListItem.StatusEnum.OFFLINE: + return FleetInfo.Status.Offline; default: - throw new DuplicateResourceException("BuildConfiguration", name); + throw new ArgumentOutOfRangeException( + nameof(statusOption), + statusOption, + $"Unrecognized remote fleet status '{statusOption}' from fleet '{fleetName}'"); } } - public async Task Create(string name, IList buildConfigurations, MultiplayConfig.FleetDefinition definition, CancellationToken cancellationToken = default) + static List FromApi(List regions) + { + return regions.Select(r => new FleetInfo.FleetRegionInfo(r.RegionID, r.RegionID, r.RegionName)).ToList(); + } + + public async Task> List(CancellationToken cancellationToken = new CancellationToken()) + { + var response = await m_FleetsApiAsync.ListFleetsAsync(m_ApiConfig.ProjectId, m_ApiConfig.EnvironmentId, cancellationToken: cancellationToken); + + var res = new List(); + + foreach (var resItem in response) + { + res.Add(new FleetInfo( + resItem.Name, + id: new FleetId { Id = resItem.Id }, + fleetStatus: FromApi(resItem.Status, resItem.Name), + osId: resItem.OsID, + osName: resItem.OsName, + regions: FromApi(resItem.Regions), + allocationStatus: FromApi(resItem.Servers) + )); + } + + return res; + } + + static FleetInfo.AllocationStatus FromApi(Servers resItemServers) + { + return new FleetInfo.AllocationStatus( + resItemServers.All.Total, + resItemServers.All.Status.Allocated, + resItemServers.All.Status.Available, + resItemServers.All.Status.Online + ); + } + + public async Task Create( + string name, + IList buildConfigurations, + MultiplayConfig.FleetDefinition definition, + CancellationToken cancellationToken) { var regions = await GetRegions(cancellationToken); @@ -46,14 +110,51 @@ public async Task Create(string name, IList build osID: Guid.Empty, // Must be set in order to avoid breaking the API osFamily: FleetCreateRequest.OsFamilyEnum.LINUX); var res = await m_FleetsApiAsync.CreateFleetAsync(m_ApiConfig.ProjectId, m_ApiConfig.EnvironmentId, fleetCreateRequest: fleet, cancellationToken: cancellationToken); - return new FleetId { Id = res.Id }; + + return new FleetInfo( + res.Name, + new FleetId { Id = res.Id }, + FromApi(res.Status, name), + res.OsID, + res.Name, + FromApi(res.FleetRegions)); + } + + static FleetInfo.Status FromApi(Fleet.StatusEnum statusOption, string fleetName) + { + switch (statusOption) + { + case Fleet.StatusEnum.ONLINE: + return FleetInfo.Status.Online; + case Fleet.StatusEnum.DRAINING: + return FleetInfo.Status.Draining; + case Fleet.StatusEnum.OFFLINE: + return FleetInfo.Status.Offline; + default: + throw new ArgumentOutOfRangeException( + nameof(statusOption), + statusOption, + $"Unrecognized remote fleet status '{statusOption}' from fleet '{fleetName}'"); + } + } + + static List FromApi(List regions) + { + return regions.Select(r => new FleetInfo.FleetRegionInfo(r.RegionID, r.RegionID, r.RegionName)).ToList(); } - public async Task Update(FleetId id, string name, IList buildConfigurations, MultiplayConfig.FleetDefinition definition, CancellationToken cancellationToken = default) + + public async Task Update( + FleetId id, + string name, + IList buildConfigurations, + MultiplayConfig.FleetDefinition definition, + Guid osId, + CancellationToken cancellationToken = new CancellationToken()) { var fleet = new FleetUpdateRequest( name: name, - osID: Guid.Empty, // Must be set in order to avoid breaking the API + osID: osId, // Must be set in order to avoid breaking the API buildConfigurations: buildConfigurations.Select(b => b.ToLong()).ToList() ); var res = await m_FleetsApiAsync.UpdateFleetAsync(m_ApiConfig.ProjectId, m_ApiConfig.EnvironmentId, id.ToGuid(), fleet, cancellationToken: cancellationToken); @@ -99,4 +200,5 @@ async Task> GetRegions(CancellationToken cancellationTo var res = await m_FleetsApiAsync.ListTemplateFleetRegionsAsync(m_ApiConfig.ProjectId, m_ApiConfig.EnvironmentId, cancellationToken: cancellationToken); return res.ToDictionary(r => r.Name, r => r.RegionID); } + } diff --git a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Unity.Services.Cli.GameServerHosting.csproj b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Unity.Services.Cli.GameServerHosting.csproj index 69f1639..c6a90f7 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Unity.Services.Cli.GameServerHosting.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.GameServerHosting/Unity.Services.Cli.GameServerHosting.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 10 enable enable @@ -23,9 +23,9 @@ - + - + diff --git a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/Common/CommonKeys.cs b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/Common/CommonKeys.cs index 26580d6..e732623 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/Common/CommonKeys.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/Common/CommonKeys.cs @@ -13,7 +13,7 @@ public static class CommonKeys public const string ValidEnvironmentName = "production"; public const string ValidEnvironmentId = "390121ca-bb43-494f-b418-55be4e0c0faf"; public const string ValidProjectId = "12345678-1111-2222-3333-123412341234"; - + public const string ValidBucketName = "ios"; public const string ValidServiceAccKeyId = "0e250400-c34a-4600-ac4b-f058b0d86b76"; public const string ValidServiceAccSecretKey = "apddVS3FPsTeN1hI_zBuHmHPF9WT2KAi"; public static string ValidAccessToken { get; } = Convert.ToBase64String( diff --git a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/AccessApiMock.cs b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/AccessApiMock.cs index 25141c8..93d5add 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/AccessApiMock.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/AccessApiMock.cs @@ -16,9 +16,9 @@ public class AccessApiMock : IServiceApiMock readonly string m_PlayerId; readonly string k_AccessModulePath = "/access/v1"; - readonly string projectPolicyUrl; - readonly string playerPolicyUrl; - readonly string allPlayerPoliciesUrl; + readonly string m_ProjectPolicyUrl; + readonly string m_PlayerPolicyUrl; + readonly string m_AllPlayerPoliciesUrl; public const string PlayerId = "j0oM0dnufzxgtwGqoH0zIpSyWUV7XUgy"; @@ -27,10 +27,10 @@ public AccessApiMock() m_ProjectId = CommonKeys.ValidProjectId; m_EnvironmentId = CommonKeys.ValidEnvironmentId; m_PlayerId = PlayerId; - projectPolicyUrl = $"{k_AccessModulePath}/projects/{m_ProjectId}/environments/{m_EnvironmentId}/resource-policy"; - playerPolicyUrl = + m_ProjectPolicyUrl = $"{k_AccessModulePath}/projects/{m_ProjectId}/environments/{m_EnvironmentId}/resource-policy"; + m_PlayerPolicyUrl = $"{k_AccessModulePath}/projects/{m_ProjectId}/environments/{m_EnvironmentId}/players/{m_PlayerId}/resource-policy"; - allPlayerPoliciesUrl = $"{k_AccessModulePath}/projects/{m_ProjectId}/environments/{m_EnvironmentId}/players/resource-policy"; + m_AllPlayerPoliciesUrl = $"{k_AccessModulePath}/projects/{m_ProjectId}/environments/{m_EnvironmentId}/players/resource-policy"; } static Policy GetPolicy() @@ -44,7 +44,7 @@ static Policy GetPolicy() "Deny", "Player", "urn:ugs:*"); - List statementLists = new List(){statement}; + List statementLists = new List() { statement }; var policy = new Policy(statementLists); return policy; @@ -80,7 +80,7 @@ public void CustomMock(WireMockServer mockServer) void MockGetProjectPolicy(WireMockServer mockServer) { - mockServer.Given(Request.Create().WithPath(projectPolicyUrl).UsingGet()) + mockServer.Given(Request.Create().WithPath(m_ProjectPolicyUrl).UsingGet()) .RespondWith(Response.Create() .WithHeaders(new Dictionary { { "Content-Type", "application/json" } }) .WithBodyAsJson(GetPolicy()) @@ -89,7 +89,7 @@ void MockGetProjectPolicy(WireMockServer mockServer) void MockGetPlayerPolicy(WireMockServer mockServer) { - mockServer.Given(Request.Create().WithPath(playerPolicyUrl).UsingGet()) + mockServer.Given(Request.Create().WithPath(m_PlayerPolicyUrl).UsingGet()) .RespondWith(Response.Create() .WithHeaders(new Dictionary { { "Content-Type", "application/json" } }) .WithBodyAsJson(GetPlayerPolicy()) @@ -98,7 +98,7 @@ void MockGetPlayerPolicy(WireMockServer mockServer) void MockGetAllPlayerPolicies(WireMockServer mockServer) { - mockServer.Given(Request.Create().WithPath(allPlayerPoliciesUrl).UsingGet()) + mockServer.Given(Request.Create().WithPath(m_AllPlayerPoliciesUrl).UsingGet()) .RespondWith(Response.Create() .WithHeaders(new Dictionary { { "Content-Type", "application/json" } }) .WithBodyAsJson(GetPlayerPolicies()) @@ -107,28 +107,28 @@ void MockGetAllPlayerPolicies(WireMockServer mockServer) void MockUpsertProjectPolicy(WireMockServer mockServer) { - mockServer.Given(Request.Create().WithPath(projectPolicyUrl).UsingPatch()) + mockServer.Given(Request.Create().WithPath(m_ProjectPolicyUrl).UsingPatch()) .RespondWith(Response.Create() .WithStatusCode(HttpStatusCode.NoContent)); } void MockUpsertPlayerPolicy(WireMockServer mockServer) { - mockServer.Given(Request.Create().WithPath(playerPolicyUrl).UsingPatch()) + mockServer.Given(Request.Create().WithPath(m_PlayerPolicyUrl).UsingPatch()) .RespondWith(Response.Create() .WithStatusCode(HttpStatusCode.NoContent)); } void MockDeleteProjectPolicyStatements(WireMockServer mockServer) { - mockServer.Given(Request.Create().WithPath($"{projectPolicyUrl}:delete-statements").UsingPost()) + mockServer.Given(Request.Create().WithPath($"{m_ProjectPolicyUrl}:delete-statements").UsingPost()) .RespondWith(Response.Create() .WithStatusCode(HttpStatusCode.NoContent)); } void MockDeletePlayerPolicyStatements(WireMockServer mockServer) { - mockServer.Given(Request.Create().WithPath($"{playerPolicyUrl}:delete-statements").UsingPost()) + mockServer.Given(Request.Create().WithPath($"{m_PlayerPolicyUrl}:delete-statements").UsingPost()) .RespondWith(Response.Create() .WithStatusCode(HttpStatusCode.NoContent)); } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/CloudCode/CloudCodeV1Mock.cs b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/CloudCode/CloudCodeV1Mock.cs index 71fc114..5bcbf1e 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/CloudCode/CloudCodeV1Mock.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/CloudCode/CloudCodeV1Mock.cs @@ -14,7 +14,7 @@ public class CloudCodeV1Mock : IServiceApiMock const string k_CloudCodeV1Config = "cloud-code-api-v1-generator-config.yaml"; public const string ValidScriptName = "test-3"; public const string ValidModuleName = "test_3"; - const string sampleURL = "https://google.com"; + const string k_SampleUrl = "https://google.com"; public async Task> CreateMappingModels() { @@ -41,14 +41,14 @@ static void MockListCSharpModule(WireMockServer mockServer) "ExistingModule", Language.CS, null, - sampleURL + k_SampleUrl ), new( "AnotherExistingModule", Language.CS, null, - sampleURL + k_SampleUrl ) }, "" @@ -68,7 +68,7 @@ static void MockHttpClientForModuleDownloads(WireMockServer mockServer) { const string testFile = "../../../../Unity.Services.Cli.CloudCode.UnitTest/ModuleTestCases/test.ccmzip"; - mockServer?.Given(Request.Create().WithUrl(sampleURL).UsingGet()) + mockServer?.Given(Request.Create().WithUrl(k_SampleUrl).UsingGet()) .RespondWith(Response.Create().WithHeaders(new Dictionary { { diff --git a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/CloudContentDeliveryApiMock.cs b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/CloudContentDeliveryApiMock.cs index a7124ce..8e3651a 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/CloudContentDeliveryApiMock.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/CloudContentDeliveryApiMock.cs @@ -17,8 +17,8 @@ public class CloudContentDeliveryApiMock : IServiceApiMock static readonly string k_Uuid = "00000000-0000-0000-0000-000000000000"; static readonly CcdGetAllByBucket200ResponseInner k_Permission = new( - "1", - "1", + "write", + "allow", "bucket/00000000-0000-0000-0000-000000000000", "user" ); @@ -72,7 +72,7 @@ public class CloudContentDeliveryApiMock : IServiceApiMock PromotedFromRelease = new Guid(k_Uuid) }; - static readonly CcdGetEntries200ResponseInner k_Entry = new() + static readonly CcdCreateOrUpdateEntryBatch200ResponseInner k_Entry = new() { Complete = true, ContentHash = "ac043a397e20f96d5ddffb8b16d5defd", @@ -101,7 +101,7 @@ public class CloudContentDeliveryApiMock : IServiceApiMock Private = true }; - static readonly List k_Entries = new() + static readonly List k_Entries = new() { k_Entry }; @@ -148,6 +148,7 @@ public void CustomMock(WireMockServer mockServer) MockGetEntry(mockServer, responseHeaders); MockGetAllEntries(mockServer, responseHeaders); MockPutEntry(mockServer, responseHeaders); + MockCreateOrUpdateEntries(mockServer, responseHeaders); MockCreateOrUpdateEntry(mockServer, responseHeaders); MockDeleteEntry(mockServer, responseHeaders); @@ -164,7 +165,7 @@ static ArrayList MockGetAllBuckets( new CcdGetBucket200Response() { Id = new Guid("00000000-0000-0000-0000-000000000000"), - Name = "test abc", + Name = CommonKeys.ValidBucketName, Description = "my description", EnvironmentName = "production", Projectguid = new Guid(CommonKeys.ValidProjectId), @@ -175,7 +176,7 @@ static ArrayList MockGetAllBuckets( new CcdGetBucket200Response() { Id = new Guid("00000000-0000-0000-0000-000000000000"), - Name = "test abc", + Name = "android", Description = "my description", EnvironmentName = "production", Projectguid = new Guid(CommonKeys.ValidProjectId), @@ -470,7 +471,7 @@ static void MockGetAllEntries( .RespondWith( Response.Create() .WithHeaders(responseHeaders) - .WithBodyAsJson(new List()) + .WithBodyAsJson(new List()) .WithStatusCode(200)); mockServer @@ -521,6 +522,24 @@ static void MockCreateOrUpdateEntry( .WithStatusCode(200)); } + static void MockCreateOrUpdateEntries( + WireMockServer mockServer, + Dictionary> responseHeaders) + { + + mockServer + .Given( + Request.Create() + .WithPath( + $"/ccd/management/v1/projects/{CommonKeys.ValidProjectId}/environments/{CommonKeys.ValidEnvironmentId}/buckets/*/batch/entries") + .UsingPost()) + .RespondWith( + Response.Create() + .WithHeaders(responseHeaders) + .WithBodyAsJson(k_Entries) + .WithStatusCode(200)); + } + static void MockPutEntry( WireMockServer mockServer, Dictionary> responseHeaders) @@ -546,7 +565,7 @@ static void MockDeleteEntry( .Given( Request.Create() .WithPath( - $"/ccd/management/v1/projects/{CommonKeys.ValidProjectId}/environments/{CommonKeys.ValidEnvironmentId}/buckets/*/entries/*") + $"/ccd/management/v1/projects/{CommonKeys.ValidProjectId}/environments/{CommonKeys.ValidEnvironmentId}/buckets/*/batch/delete/entries") .UsingDelete()) .RespondWith( Response.Create() diff --git a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/CloudSaveApiMock.cs b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/CloudSaveApiMock.cs index 8dcae7c..bd684bc 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/CloudSaveApiMock.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/CloudSaveApiMock.cs @@ -16,9 +16,9 @@ public class CloudSaveApiMock : IServiceApiMock const string k_CloudSaveDataPath = "data"; const string k_BaseUrl = $"{k_CloudSavePath}/{k_CloudSaveDataPath}/projects/{CommonKeys.ValidProjectId}/environments/{CommonKeys.ValidEnvironmentId}"; - static readonly GetIndexIdsResponse k_GetIndexIdsResponse = new GetIndexIdsResponse( - new List - { + static readonly GetIndexIdsResponse k_GetIndexIdsResponse = new GetIndexIdsResponse( + new List + { new LiveIndexConfigInner( "testIndex1", LiveIndexConfigInner.EntityTypeEnum.Player, @@ -39,8 +39,8 @@ public class CloudSaveApiMock : IServiceApiMock new IndexField("testIndexKey2", false) } ), - } - ); + } + ); static readonly GetCustomIdsResponse k_GetCustomIdsResponse = new( new List @@ -57,6 +57,21 @@ public class CloudSaveApiMock : IServiceApiMock new GetPlayersWithDataResponseLinks("someLink") ); + static readonly GetPlayersWithDataResponse k_GetPlayerIdsResponse = new( + new List + { + new( + "testId1", + new AccessClassesWithMetadata( + null, new AccessClassMetadata(1, 100), null, new AccessClassMetadata(2, 200))), + new( + "testId1", + new AccessClassesWithMetadata( + null, new AccessClassMetadata(1, 100), null, new AccessClassMetadata(2, 200))), + }, + new GetPlayersWithDataResponseLinks("someLink") + ); + static readonly List k_ValidQueryResponseList = new List() { new QueryIndexResponseResultsInner( @@ -84,11 +99,11 @@ public class CloudSaveApiMock : IServiceApiMock new IndexField("key2", false) }; - static readonly CreateIndexBody k_ValidCreatePlayerIndexBody = new CreateIndexBody ( + static readonly CreateIndexBody k_ValidCreateIndexBody = new CreateIndexBody( new CreateIndexBodyIndexConfig(k_ValidIndexFields)); static readonly CreateIndexResponse k_ValidCreateIndexResponse = new CreateIndexResponse("id", IndexStatus.READY); - + public async Task> CreateMappingModels() { var cloudsaveServiceModels = await MappingModelUtils.ParseMappingModelsFromGeneratorConfigAsync("cloud-save-api-v1-generator-config.yaml", new()); @@ -117,6 +132,18 @@ static void MockListCustomIds(WireMockServer mockServer) .WithStatusCode(HttpStatusCode.OK)); } + + static void MockListPlayerIds(WireMockServer mockServer) + { + mockServer.Given(Request.Create() + .WithPath($"{k_BaseUrl}/players") + .UsingGet()) + .RespondWith(Response.Create() + .WithHeaders(new Dictionary { { "Content-Type", "application/json" } }) + .WithBodyAsJson(k_GetPlayerIdsResponse) + .WithStatusCode(HttpStatusCode.OK)); + } + static void MockPlayerQueries(WireMockServer mockServer) { mockServer.Given(Request.Create() @@ -160,6 +187,7 @@ static void MockCustomDataQueries(WireMockServer mockServer) .WithStatusCode(HttpStatusCode.OK)); } + static void MockCreatePlayerIndexes(WireMockServer mockServer) { mockServer.Given(Request.Create() @@ -185,12 +213,32 @@ static void MockCreatePlayerIndexes(WireMockServer mockServer) .WithStatusCode(HttpStatusCode.OK)); } + static void MockCreateCustomIndexes(WireMockServer mockServer) + { + mockServer.Given(Request.Create() + .WithPath($"{k_BaseUrl}/indexes/custom") + .UsingPost()) + .RespondWith(Response.Create() + .WithHeaders(new Dictionary { { "Content-Type", "application/json" } }) + .WithBodyAsJson(k_ValidCreateIndexResponse) + .WithStatusCode(HttpStatusCode.OK)); + mockServer.Given(Request.Create() + .WithPath($"{k_BaseUrl}/indexes/custom/private") + .UsingPost()) + .RespondWith(Response.Create() + .WithHeaders(new Dictionary { { "Content-Type", "application/json" } }) + .WithBodyAsJson(k_ValidCreateIndexResponse) + .WithStatusCode(HttpStatusCode.OK)); + } + public void CustomMock(WireMockServer mockServer) { MockListIndexes(mockServer); MockListCustomIds(mockServer); + MockListPlayerIds(mockServer); MockPlayerQueries(mockServer); MockCustomDataQueries(mockServer); + MockCreateCustomIndexes(mockServer); MockCreatePlayerIndexes(mockServer); mockServer.AllowPartialMapping(); } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/LeaderboardApiMock.cs b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/LeaderboardApiMock.cs index 75d1d8e..ea3afa3 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/LeaderboardApiMock.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/ServiceMocks/LeaderboardApiMock.cs @@ -20,7 +20,7 @@ public class LeaderboardApiMock : IServiceApiMock UpdateType.Aggregate, 10, new ResetConfig(new DateTime(2023, 1, 1), "@1d", true), - new TieringConfig(TieringConfig.StrategyEnum.Percent, new List(){new TieringConfigTiersInner("tier1", 2)}), + new TieringConfig(TieringConfig.StrategyEnum.Percent, new List() { new TieringConfigTiersInner("tier1", 2) }), versions: new List() ); public static readonly UpdatedLeaderboardConfig Leaderboard2 = new( @@ -38,7 +38,7 @@ public class LeaderboardApiMock : IServiceApiMock UpdateType.Aggregate, 10, new ResetConfig(new DateTime(2023, 1, 1), "@1d", true), - new TieringConfig(TieringConfig.StrategyEnum.Percent, new List(){new TieringConfigTiersInner("tier1", 2)}), + new TieringConfig(TieringConfig.StrategyEnum.Percent, new List() { new TieringConfigTiersInner("tier1", 2) }), versions: new List() ); public static readonly UpdatedLeaderboardConfig Leaderboard4 = new( @@ -48,7 +48,7 @@ public class LeaderboardApiMock : IServiceApiMock UpdateType.Aggregate, 10, new ResetConfig(new DateTime(2023, 1, 1), "@1d", true), - new TieringConfig(TieringConfig.StrategyEnum.Percent, new List(){new TieringConfigTiersInner("tier1", 2)}), + new TieringConfig(TieringConfig.StrategyEnum.Percent, new List() { new TieringConfigTiersInner("tier1", 2) }), versions: new List() ); public static readonly UpdatedLeaderboardConfig Leaderboard5 = new( @@ -58,7 +58,7 @@ public class LeaderboardApiMock : IServiceApiMock UpdateType.Aggregate, 10, new ResetConfig(new DateTime(2023, 1, 1), "@1d", true), - new TieringConfig(TieringConfig.StrategyEnum.Percent, new List(){new TieringConfigTiersInner("tier1", 2)}), + new TieringConfig(TieringConfig.StrategyEnum.Percent, new List() { new TieringConfigTiersInner("tier1", 2) }), versions: new List() ); public static readonly UpdatedLeaderboardConfig Leaderboard6 = new( @@ -68,7 +68,7 @@ public class LeaderboardApiMock : IServiceApiMock UpdateType.Aggregate, 10, new ResetConfig(new DateTime(2023, 1, 1), "@1d", true), - new TieringConfig(TieringConfig.StrategyEnum.Percent, new List(){new TieringConfigTiersInner("tier1", 2)}), + new TieringConfig(TieringConfig.StrategyEnum.Percent, new List() { new TieringConfigTiersInner("tier1", 2) }), versions: new List() ); public static readonly UpdatedLeaderboardConfig Leaderboard7 = new( @@ -78,7 +78,7 @@ public class LeaderboardApiMock : IServiceApiMock UpdateType.Aggregate, 10, new ResetConfig(new DateTime(2023, 1, 1), "@1d", true), - new TieringConfig(TieringConfig.StrategyEnum.Percent, new List(){new TieringConfigTiersInner("tier1", 2)}), + new TieringConfig(TieringConfig.StrategyEnum.Percent, new List() { new TieringConfigTiersInner("tier1", 2) }), versions: new List() ); public static readonly UpdatedLeaderboardConfig Leaderboard8 = new( @@ -88,7 +88,7 @@ public class LeaderboardApiMock : IServiceApiMock UpdateType.Aggregate, 10, new ResetConfig(new DateTime(2023, 1, 1), "@1d", true), - new TieringConfig(TieringConfig.StrategyEnum.Percent, new List(){new TieringConfigTiersInner("tier1", 2)}), + new TieringConfig(TieringConfig.StrategyEnum.Percent, new List() { new TieringConfigTiersInner("tier1", 2) }), versions: new List() ); public static readonly UpdatedLeaderboardConfig Leaderboard9 = new( @@ -98,7 +98,7 @@ public class LeaderboardApiMock : IServiceApiMock UpdateType.Aggregate, 10, new ResetConfig(new DateTime(2023, 1, 1), "@1d", true), - new TieringConfig(TieringConfig.StrategyEnum.Percent, new List(){new TieringConfigTiersInner("tier1", 2)}), + new TieringConfig(TieringConfig.StrategyEnum.Percent, new List() { new TieringConfigTiersInner("tier1", 2) }), versions: new List() ); public static readonly UpdatedLeaderboardConfig Leaderboard10 = new( @@ -108,7 +108,7 @@ public class LeaderboardApiMock : IServiceApiMock UpdateType.Aggregate, 10, new ResetConfig(new DateTime(2023, 1, 1), "@1d", true), - new TieringConfig(TieringConfig.StrategyEnum.Percent, new List(){new TieringConfigTiersInner("tier1", 2)}), + new TieringConfig(TieringConfig.StrategyEnum.Percent, new List() { new TieringConfigTiersInner("tier1", 2) }), versions: new List() ); public static readonly UpdatedLeaderboardConfig Leaderboard11 = new( @@ -118,7 +118,7 @@ public class LeaderboardApiMock : IServiceApiMock UpdateType.Aggregate, 10, new ResetConfig(new DateTime(2023, 1, 1), "@1d", true), - new TieringConfig(TieringConfig.StrategyEnum.Percent, new List(){new TieringConfigTiersInner("tier1", 2)}), + new TieringConfig(TieringConfig.StrategyEnum.Percent, new List() { new TieringConfigTiersInner("tier1", 2) }), versions: new List() ); public static readonly UpdatedLeaderboardConfig Leaderboard12 = new( @@ -128,19 +128,20 @@ public class LeaderboardApiMock : IServiceApiMock UpdateType.Aggregate, 10, new ResetConfig(new DateTime(2023, 1, 1), "@1d", true), - new TieringConfig(TieringConfig.StrategyEnum.Percent, new List(){new TieringConfigTiersInner("tier1", 2)}), + new TieringConfig(TieringConfig.StrategyEnum.Percent, new List() { new TieringConfigTiersInner("tier1", 2) }), versions: new List() ); - readonly string BaseUrl; + readonly string m_BaseUrl; - readonly Dictionary RequestHeader = new () + readonly Dictionary m_RequestHeader = new() { { "Content-Type", "application/json" } }; - public LeaderboardApiMock() { - BaseUrl = $"{k_LeaderboardPath}/projects/{CommonKeys.ValidProjectId}/environments/{CommonKeys.ValidEnvironmentId}/leaderboards"; + public LeaderboardApiMock() + { + m_BaseUrl = $"{k_LeaderboardPath}/projects/{CommonKeys.ValidProjectId}/environments/{CommonKeys.ValidEnvironmentId}/leaderboards"; } public Task> CreateMappingModels() @@ -173,53 +174,53 @@ void MockListLeaderboards(WireMockServer mockServer, List { { "Content-Type", "application/json" } }) .WithBodyAsJson(m_GetResponse) @@ -120,14 +120,14 @@ public void MockGetAllConfigsFromEnvironmentAsync(WireMockServer mockServer, str public void MockUpdateConfigAsync(WireMockServer mockServer, string configId) { mockServer - .Given(Request.Create().WithPath($"{UpdateConfigUrl}/{configId}").UsingPut()) + .Given(Request.Create().WithPath($"{m_UpdateConfigUrl}/{configId}").UsingPut()) .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.NoContent)); } public void MockDeleteConfigAsync(WireMockServer mockServer, string configId) { mockServer - .Given(Request.Create().WithPath($"{DeleteConfigUrl}/{configId}").UsingDelete()) + .Given(Request.Create().WithPath($"{m_DeleteConfigUrl}/{configId}").UsingDelete()) .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.NoContent)); } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/Unity.Services.Cli.Integration.MockServer.csproj b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/Unity.Services.Cli.Integration.MockServer.csproj index 5bd4f2e..ac3b26b 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/Unity.Services.Cli.Integration.MockServer.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServer/Unity.Services.Cli.Integration.MockServer.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable enable true @@ -9,12 +9,13 @@ true - + + - - + + diff --git a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServerApp/Unity.Services.Cli.Integration.MockServerApp.csproj b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServerApp/Unity.Services.Cli.Integration.MockServerApp.csproj index 4854ec4..e3f6227 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServerApp/Unity.Services.Cli.Integration.MockServerApp.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Integration.MockServerApp/Unity.Services.Cli.Integration.MockServerApp.csproj @@ -1,7 +1,7 @@ Exe - net6.0 + net8.0 enable enable true diff --git a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/AccessTests/AccessTests.cs b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/AccessTests/AccessTests.cs index 9d2c3ff..9c24ee8 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/AccessTests/AccessTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/AccessTests/AccessTests.cs @@ -1,12 +1,10 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; using Newtonsoft.Json; using NUnit.Framework; using Unity.Services.Cli.Common.Exceptions; using Unity.Services.Cli.Common.Models; -using Unity.Services.Cli.Common.Networking; using Unity.Services.Cli.IntegrationTest.Common; using Unity.Services.Cli.MockServer; using Unity.Services.Cli.MockServer.Common; diff --git a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Deploy/ProjectAccess/ProjectAccessDeployTests.cs b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Deploy/ProjectAccess/ProjectAccessDeployTests.cs index 5a8e15c..20e0e0b 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Deploy/ProjectAccess/ProjectAccessDeployTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Deploy/ProjectAccess/ProjectAccessDeployTests.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using System.Threading.Tasks; using System.Collections.Generic; -using Unity.Services.Access.Authoring.Core.Model; using Unity.Services.Cli.Authoring.Model; using Unity.Services.DeploymentApi.Editor; using Unity.Services.Cli.Common.Exceptions; diff --git a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Fetch/LeaderboardFetchTests.cs b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Fetch/LeaderboardFetchTests.cs index 72ae1c8..9a90531 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Fetch/LeaderboardFetchTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Fetch/LeaderboardFetchTests.cs @@ -36,12 +36,12 @@ public async Task SetUp() await MockApi.MockServiceAsync(new LeaderboardApiMock()); await MockApi.MockServiceAsync(new IdentityV1Mock()); Directory.CreateDirectory(k_TestDirectory); - m_LocalLeaderboards = new LeaderboardConfig[] + m_LocalLeaderboards = new LeaderboardConfig[] { new ("lb1", "leaderboard 1") { Path = Path.Combine(k_TestDirectory, "lb1.lb") } }; - m_RemoteLeaderboards = new LeaderboardConfig[] + m_RemoteLeaderboards = new LeaderboardConfig[] { new ("lb1", "leaderboard 1") { Path = Path.Combine(k_TestDirectory, "lb1.lb") }, new ("lb2", "leaderboard 2") { Path = Path.Combine(k_TestDirectory, "lb2.lb") } @@ -76,10 +76,10 @@ public async Task FetchToValidConfigFromDirectorySucceeds() var localLeaderboards = m_LocalLeaderboards!; await CreateDeployTestFilesAsync(localLeaderboards); var expectedResult = new FetchResult( - updated: new IDeploymentItem[]{ localLeaderboards[0] }, + updated: new IDeploymentItem[] { localLeaderboards[0] }, deleted: Array.Empty(), created: Array.Empty(), - authored: new IDeploymentItem[]{ localLeaderboards[0] }, + authored: new IDeploymentItem[] { localLeaderboards[0] }, failed: Array.Empty() ); await GetFullySetCli() @@ -95,10 +95,10 @@ public async Task FetchToValidConfigFromDirectoryReconcileSucceeds() var localLeaderboards = m_LocalLeaderboards!; await CreateDeployTestFilesAsync(localLeaderboards); var expectedResult = new FetchResult( - updated: new IDeploymentItem[]{ localLeaderboards[0] }, + updated: new IDeploymentItem[] { localLeaderboards[0] }, deleted: Array.Empty(), - created: new IDeploymentItem[]{ m_RemoteLeaderboards![1] }, - authored: new IDeploymentItem[]{ localLeaderboards[0], m_RemoteLeaderboards![1] }, + created: new IDeploymentItem[] { m_RemoteLeaderboards![1] }, + authored: new IDeploymentItem[] { localLeaderboards[0], m_RemoteLeaderboards![1] }, failed: Array.Empty() ); await GetFullySetCli() @@ -114,7 +114,7 @@ public async Task FetchToValidConfigFromDirectoryDryRunSucceeds() var localLeaderboards = m_LocalLeaderboards!; await CreateDeployTestFilesAsync(localLeaderboards); var expectedResult = new FetchResult( - updated: new IDeploymentItem[]{ localLeaderboards[0] }, + updated: new IDeploymentItem[] { localLeaderboards[0] }, deleted: Array.Empty(), created: Array.Empty(), authored: Array.Empty(), @@ -134,9 +134,9 @@ public async Task FetchToValidConfigFromDirectoryDryRunWithReconcileSucceeds() var localLeaderboards = m_LocalLeaderboards!; await CreateDeployTestFilesAsync(localLeaderboards); var expectedResult = new FetchResult( - updated: new IDeploymentItem[]{ localLeaderboards[0] }, + updated: new IDeploymentItem[] { localLeaderboards[0] }, deleted: Array.Empty(), - created: new IDeploymentItem[]{ m_RemoteLeaderboards![1] }, + created: new IDeploymentItem[] { m_RemoteLeaderboards![1] }, authored: Array.Empty(), failed: Array.Empty(), dryRun: true @@ -172,7 +172,7 @@ public async Task FetchToValidConfigFromDuplicateIdFails() deleted: Array.Empty(), created: Array.Empty(), authored: Array.Empty(), - failed: new IDeploymentItem[]{ localLeaderboards[0], localLeaderboards[1] } + failed: new IDeploymentItem[] { localLeaderboards[0], localLeaderboards[1] } ); await GetFullySetCli() .Command($"fetch {k_TestDirectory} -s leaderboards") diff --git a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Fetch/ProjectAccessFetchTests.cs b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Fetch/ProjectAccessFetchTests.cs index 31031db..23f484c 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Fetch/ProjectAccessFetchTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Fetch/ProjectAccessFetchTests.cs @@ -64,7 +64,7 @@ public void TearDown() } static async Task CreateDeployTestFilesAsync( - IReadOnlyList testCases ,ICollection contents) + IReadOnlyList testCases, ICollection contents) { foreach (var testCase in testCases) { diff --git a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Fetch/TriggersFetchTests.cs b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Fetch/TriggersFetchTests.cs index fd546cc..a7ee258 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Fetch/TriggersFetchTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Authoring/Fetch/TriggersFetchTests.cs @@ -92,10 +92,10 @@ public async Task FetchToValidConfigFromDirectorySucceeds() var localTriggers = m_LocalTriggers!; await CreateDeployFileAsync(localTriggers); var expectedResult = new TriggersFetchResult( - updated: new IDeploymentItem[]{ localTriggers[0] }, + updated: new IDeploymentItem[] { localTriggers[0] }, deleted: Array.Empty(), created: Array.Empty(), - authored: new IDeploymentItem[]{new TriggersFileItem(null!, localTriggers[0].Path)}, + authored: new IDeploymentItem[] { new TriggersFileItem(null!, localTriggers[0].Path) }, failed: Array.Empty() ); await GetFullySetCli() @@ -111,9 +111,9 @@ public async Task FetchToValidConfigFromDirectoryReconcileSucceeds() var localTriggers = m_LocalTriggers!; await CreateDeployFileAsync(localTriggers); var expectedResult = new TriggersFetchResult( - updated: new IDeploymentItem[]{ localTriggers[0] }, + updated: new IDeploymentItem[] { localTriggers[0] }, deleted: Array.Empty(), - created: new IDeploymentItem[]{ m_RemoteTriggers![1] }, + created: new IDeploymentItem[] { m_RemoteTriggers![1] }, authored: new IDeploymentItem[] { new TriggersFileItem(null!, localTriggers[0].Path), @@ -134,10 +134,10 @@ public async Task FetchToValidConfigFromDirectoryDryRunSucceeds() var localTriggers = m_LocalTriggers!; await CreateDeployFileAsync(localTriggers); var expectedResult = new TriggersFetchResult( - updated: new IDeploymentItem[]{ localTriggers[0] }, + updated: new IDeploymentItem[] { localTriggers[0] }, deleted: Array.Empty(), created: Array.Empty(), - authored: new IDeploymentItem[]{new TriggersFileItem(null!, localTriggers[0].Path)}, + authored: new IDeploymentItem[] { new TriggersFileItem(null!, localTriggers[0].Path) }, failed: Array.Empty(), dryRun: true ); @@ -156,9 +156,9 @@ public async Task FetchToValidConfigFromDirectoryDryRunWithReconcileSucceeds() var localTriggers = m_LocalTriggers!; await CreateDeployFileAsync(localTriggers); var expectedResult = new TriggersFetchResult( - updated: new IDeploymentItem[]{ localTriggers[0] }, + updated: new IDeploymentItem[] { localTriggers[0] }, deleted: Array.Empty(), - created: new IDeploymentItem[]{ m_RemoteTriggers![1] }, + created: new IDeploymentItem[] { m_RemoteTriggers![1] }, authored: new IDeploymentItem[] { new TriggersFileItem(null!, localTriggers[0].Path), @@ -198,7 +198,7 @@ public async Task FetchToValidConfigFromDuplicateIdFails() deleted: Array.Empty(), created: Array.Empty(), authored: Array.Empty(), - failed: new IDeploymentItem[]{ localTriggers[0], localTriggers[1] } + failed: new IDeploymentItem[] { localTriggers[0], localTriggers[1] } ); await GetFullySetCli() .DebugCommand($"fetch {k_TestDirectory} -s triggers") diff --git a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryBadgeTests.cs b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryBadgeTests.cs new file mode 100644 index 0000000..b0ec1d4 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryBadgeTests.cs @@ -0,0 +1,108 @@ +using System.IO; +using System.Threading.Tasks; +using NUnit.Framework; +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Cli.MockServer.Common; +using Unity.Services.Cli.MockServer.ServiceMocks; + +namespace Unity.Services.Cli.IntegrationTest.CloudContentDeliveryTests; + +[TestFixture] +public class CloudContentDeliveryBadgeTests : UgsCliFixture +{ + + [SetUp] + public async Task SetUp() + { + DeleteLocalConfig(); + DeleteLocalCredentials(); + + await MockApi.MockServiceAsync(new IdentityV1Mock()); + await MockApi.MockServiceAsync(new CloudContentDeliveryApiMock()); + + SetConfigValue("project-id", CommonKeys.ValidProjectId); + SetConfigValue("environment-name", CommonKeys.ValidEnvironmentName); + SetConfigValue("bucket-name", CommonKeys.ValidBucketName); + } + + [TearDown] + public void TearDown() + { + MockApi.Server?.ResetMappings(); + } + + [Test] + public async Task CloudContentDeliveryBadgeBadCommand() + { + await GetLoggedInCli() + .Command("ccd badges badcommand") + .AssertStandardErrorContains("Unrecognized command or argument 'badcommand'.") + .AssertExitCode(ExitCode.HandledError) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryBadgeBadOption() + { + await GetLoggedInCli() + .Command("ccd badges list --badOption") + .AssertStandardErrorContains("Unrecognized command or argument '--badOption'.") + .AssertExitCode(ExitCode.HandledError) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryBadgeList() + { + await GetLoggedInCli() + .Command("ccd badges list") + .AssertStandardOutputContains( + "name: badge 1") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryBadgeListInJson() + { + await GetLoggedInCli() + .Command("ccd badges list --json") + .AssertStandardOutputContains("Name\": \"badge 1\"") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryBadgeListWithFiltersAndParameters() + { + await GetLoggedInCli() + .Command("ccd badges list -pa 1 -pp 10 -n badge -s name -o asc") + .AssertStandardOutputContains( + "name: badge 1") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryBadgeCreate() + { + await GetLoggedInCli() + .Command("ccd badges create 1 mybadge") + .AssertNoErrors() + .AssertStandardOutputContains( + "name: badge 1") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryBadgeDelete() + { + await GetLoggedInCli() + .Command("ccd badges delete mybadge") + .AssertStandardOutputContains("Deleting badge...") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryBucketTests.cs b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryBucketTests.cs new file mode 100644 index 0000000..2c6f026 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryBucketTests.cs @@ -0,0 +1,155 @@ +using System.IO; +using System.Threading.Tasks; +using NUnit.Framework; +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Cli.MockServer.Common; +using Unity.Services.Cli.MockServer.ServiceMocks; + +namespace Unity.Services.Cli.IntegrationTest.CloudContentDeliveryTests; + +[TestFixture] +public class CloudContentDeliveryBucketTests : UgsCliFixture +{ + static readonly string k_TestDirectory = Path.Combine(UgsCliBuilder.RootDirectory, ".tmp/FilesDir"); + + [SetUp] + public async Task SetUp() + { + DeleteLocalConfig(); + DeleteLocalCredentials(); + if (!Directory.Exists(k_TestDirectory)) + Directory.CreateDirectory(k_TestDirectory); + + await MockApi.MockServiceAsync(new IdentityV1Mock()); + await MockApi.MockServiceAsync(new CloudContentDeliveryApiMock()); + + SetConfigValue("project-id", CommonKeys.ValidProjectId); + SetConfigValue("environment-name", CommonKeys.ValidEnvironmentName); + + } + + [TearDown] + public void TearDown() + { + MockApi.Server?.ResetMappings(); + } + + [Test] + public async Task CloudContentDeliveryBucketsListReturnsJsonResponse() + { + await GetLoggedInCli() + .Command("ccd buckets list --json") + .AssertStandardOutputContains( + "\"Id\": \"00000000-0000-0000-0000-000000000000\"") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryBucketsList() + { + await GetLoggedInCli() + .Command("ccd buckets list") + .AssertStandardOutputContains( + "id: 00000000-0000-0000-0000-000000000000") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryBucketsListWithFiltersAndParameters() + { + var filterName = "TestBucket"; + var page = 1; + var perPage = 10; + var sortBy = "name"; + var sortOrder = "asc"; + await GetLoggedInCli() + .Command( + $"ccd buckets list --filter-by-name {filterName} --page {page} --per-page {perPage} --sort-by {sortBy} --sort-order {sortOrder}") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + + } + + [Test] + public async Task CloudContentDeliveryBucketsCreate() + { + var bucketName = "TestBucket"; + var bucketDescription = "Test bucket description"; + await GetLoggedInCli() + .Command($"ccd buckets create {bucketName} --description \"{bucketDescription}\" --private") + .AssertNoErrors() + .AssertStandardOutputContains("id: 00000000-0000-0000-0000-000000000000") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + + } + + [Test] + public async Task CloudContentDeliveryBucketsInfo() + { + + await GetLoggedInCli() + .Command($"ccd buckets info ios") + .AssertNoErrors() + .AssertStandardOutputContains( + "id: 00000000-0000-0000-0000-000000000000") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryBucketsDelete() + { + + await GetLoggedInCli() + .Command($"ccd buckets delete ios") + .AssertNoErrors() + .AssertStandardOutputContains("Bucket deleted.") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + + } + + [Test] + public async Task CloudContentDeliveryBadCommand() + { + await GetLoggedInCli() + .Command("ccd bad command") + .AssertStandardOutputContains("'bad' was not matched.") + .AssertExitCode(ExitCode.HandledError) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryPromoteOnlyTrue() + { + await GetLoggedInCli() + .Command("ccd buckets permissions update ios --action=Write -m Allow -r User") + .AssertStandardOutputContains("action: write") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryMissingRequiredPromoteOnlyOption() + { + await GetLoggedInCli() + .Command("ccd buckets permissions update ios") + .AssertStandardErrorContains("Option '-a' is required.") + .AssertExitCode(ExitCode.HandledError) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryPromoteOnlyFalse() + { + await GetLoggedInCli() + .Command("ccd buckets permissions update ios --action=Write -m Deny -r User") + .AssertStandardOutputContains("action: write") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryEntryTests.cs b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryEntryTests.cs new file mode 100644 index 0000000..0b5e151 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryEntryTests.cs @@ -0,0 +1,183 @@ +using System.IO; +using System.Threading.Tasks; +using NUnit.Framework; +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Cli.MockServer.Common; +using Unity.Services.Cli.MockServer.ServiceMocks; + +namespace Unity.Services.Cli.IntegrationTest.CloudContentDeliveryTests; + +[TestFixture] +public class CloudContentDeliveryEntryTests : UgsCliFixture +{ + static readonly string k_TestDirectory = Path.Combine(UgsCliBuilder.RootDirectory, ".tmp/FilesDir"); + + [SetUp] + public async Task SetUp() + { + DeleteLocalConfig(); + DeleteLocalCredentials(); + if (!Directory.Exists(k_TestDirectory)) + Directory.CreateDirectory(k_TestDirectory); + + await File.WriteAllTextAsync(Path.Join(k_TestDirectory, "foo"), "{}"); + await File.WriteAllTextAsync(Path.Join(k_TestDirectory, "image.jpg"), "{}"); + await MockApi.MockServiceAsync(new IdentityV1Mock()); + await MockApi.MockServiceAsync(new CloudContentDeliveryApiMock()); + + SetConfigValue("project-id", CommonKeys.ValidProjectId); + SetConfigValue("environment-name", CommonKeys.ValidEnvironmentName); + SetConfigValue("bucket-name", CommonKeys.ValidBucketName); + } + + [TearDown] + public void TearDown() + { + MockApi.Server?.ResetMappings(); + } + + [Test] + public async Task CloudContentDeliveryListEntries() + { + await GetLoggedInCli() + .Command("ccd entries list") + .AssertStandardOutputContains("id: 00000000-0000-0000-0000-000000000000") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryListEntriesWithParams() + { + await GetLoggedInCli() + .Command( + "ccd entries list --page=1 --per-page=10 --label=abc --sort-by=path --sort-order=desc") + .AssertStandardOutputContains("id: 00000000-0000-0000-0000-000000000000") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryListEntriesWithParamsInJson() + { + await GetLoggedInCli() + .Command( + "ccd entries list --page=1 --per-page=10 --label=def --sort-by=content_size --sort-order=asc --json") + .AssertStandardOutputContains( + "\"Id\": \"00000000-0000-0000-0000-000000000000\"") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryInfoEntries() + { + await GetLoggedInCli() + .DebugCommand("ccd entries info myentry") + .AssertStandardOutputContains("entryid: 00000000-0000-0000-0000-000000000000") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryUpdateEntries() + { + await GetLoggedInCli() + .Command("ccd entries update myentry -l mylabel") + .AssertStandardOutputContains( + "- my label") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryDeleteEntries() + { + await GetLoggedInCli() + .Command("ccd entries delete myentry") + .AssertStandardOutputContains("Deleting entry...") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliverySyncEntriesWithParams() + { + await GetLoggedInCli() + .DebugCommand( + $"ccd entries sync {k_TestDirectory} -r -u mybadge") + .AssertStandardOutputContains("operationCompletedSuccessfully: true") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliverySyncEntriesWithParamsIncludeSyncEntriesOnlyFalse() + { + await GetLoggedInCli() + .DebugCommand( + $"ccd entries sync {k_TestDirectory} -r -u mybadge --include-entries-added-during-sync") + .AssertStandardOutputContains("operationCompletedSuccessfully: true") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliverySyncEntriesWithBadgeButNoRelease() + { + await GetLoggedInCli() + .DebugCommand( + $"ccd entries sync {k_TestDirectory} -u mybadge") + .AssertStandardErrorContains("The badge option requires the 'create release' option to be set to true.") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliverySyncEntriesWithReleaseNoteButNoRelease() + { + await GetLoggedInCli() + .DebugCommand( + $"ccd entries sync {k_TestDirectory} -n mynotes") + .AssertStandardErrorContains("The release notes option requires the 'create release' option to be set to true. As a result, no release notes were added.") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryDownloadEntries() + { + await GetLoggedInCli() + .Command( + $"ccd entries download myentry") + .AssertStandardOutputContains("Downloading entry content...") + .AssertNoErrors() + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryCopyEntries() + { + await GetLoggedInCli() + .Command( + $"ccd entries copy {k_TestDirectory}/foo foo") + .AssertStandardOutputContains( + "entryid: 00000000-0000-0000-0000-000000000000") + .AssertNoErrors() + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryCopyEntriesMissingArgument() + { + await GetLoggedInCli() + .Command( + $"ccd entries copy {k_TestDirectory}/foo") + .AssertStandardErrorContains("Required argument missing for command: 'copy'.") + .AssertExitCode(ExitCode.HandledError) + .ExecuteAsync(); + } + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryReleaseTests.cs b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryReleaseTests.cs new file mode 100644 index 0000000..abfad6d --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/CloudContentDeliveryTests/CloudContentDeliveryReleaseTests.cs @@ -0,0 +1,127 @@ +using System.IO; +using System.Threading.Tasks; +using NUnit.Framework; +using Unity.Services.Cli.Common.Exceptions; +using Unity.Services.Cli.MockServer.Common; +using Unity.Services.Cli.MockServer.ServiceMocks; + +namespace Unity.Services.Cli.IntegrationTest.CloudContentDeliveryTests; + +[TestFixture] +public class CloudContentDeliveryReleaseTests : UgsCliFixture +{ + static readonly string k_TestDirectory = Path.Combine(UgsCliBuilder.RootDirectory, ".tmp/FilesDir"); + + [SetUp] + public async Task SetUp() + { + DeleteLocalConfig(); + DeleteLocalCredentials(); + if (!Directory.Exists(k_TestDirectory)) + Directory.CreateDirectory(k_TestDirectory); + + await MockApi.MockServiceAsync(new IdentityV1Mock()); + await MockApi.MockServiceAsync(new CloudContentDeliveryApiMock()); + + SetConfigValue("project-id", CommonKeys.ValidProjectId); + SetConfigValue("environment-name", CommonKeys.ValidEnvironmentName); + SetConfigValue("bucket-name", CommonKeys.ValidBucketName); + + } + + [TearDown] + public void TearDown() + { + MockApi.Server?.ResetMappings(); + } + + [Test] + public async Task CloudContentDeliveryCreateRelease() + { + await GetLoggedInCli() + .Command("ccd releases create") + .AssertStandardOutputContains( + "releaseid: 00000000-0000-0000-0000-000000000000") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryUpdateRelease() + { + await GetLoggedInCli() + .Command( + "ccd releases update 1 --notes=\"my note\"") + .AssertStandardOutputContains( + "notes: my note") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryUpdateReleaseMissingRequiredOption() + { + await GetLoggedInCli() + .Command( + "ccd releases update 1") + .AssertStandardErrorContains("Option '-n' is required.") + .AssertExitCode(ExitCode.HandledError) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryListReleases() + { + await GetLoggedInCli() + .Command("ccd releases list") + .AssertStandardOutputContains("releaseId: 00000000-0000-0000-0000-000000000000") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryListReleasesWithParams() + { + await GetLoggedInCli() + .Command( + "ccd releases list --page=1 --per-page=10 --release-number=1 --notes=abc --badges=abc --sort-by=created --sort-order=desc") + .AssertStandardOutputContains("releaseId: 00000000-0000-0000-0000-000000000000") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryGetReleaseInfo() + { + await GetLoggedInCli() + .Command("ccd releases info 1") + .AssertStandardOutputContains( + "releasenum: 1") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryPromoteBucket() + { + await GetLoggedInCli() + .Command( + "ccd releases promote 1 ios production") + .AssertStandardOutputContains("promotionId: 00000000-0000-0000-0000-000000000000") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + + [Test] + public async Task CloudContentDeliveryGetPromotionStatus() + { + await GetLoggedInCli() + .Command( + "ccd releases promotions status 00000000-0000-0000-0000-000000000000") + .AssertStandardOutputContains( + "promotionStatus: Complete") + .AssertExitCode(ExitCode.Success) + .ExecuteAsync(); + } + +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/ConfigTests/ConfigTests.cs b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/ConfigTests/ConfigTests.cs index 0259bba..a78b472 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/ConfigTests/ConfigTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/ConfigTests/ConfigTests.cs @@ -84,7 +84,7 @@ public async Task ConfigSetProjectIdFails() [Test] public async Task ConfigSetWithInvalidKeyErrorsOut() { - const string expectedError = "key 'invalid-key' not allowed. Allowed values: environment-name,project-id,bucket-id"; + const string expectedError = "key 'invalid-key' not allowed. Allowed values: environment-name,project-id,bucket-name"; await new UgsCliTestCase() .Command("config set invalid-key random-value") diff --git a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/EnvTests/EnvTests.cs b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/EnvTests/EnvTests.cs index d1b777f..f442d0e 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/EnvTests/EnvTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/EnvTests/EnvTests.cs @@ -55,12 +55,15 @@ public async Task EnvironmentListThrowsNotLoggedInException() [Test] public async Task EnvironmentListReturnsZeroExitCode() { - var expectedReturn = $"\"{CommonKeys.ValidEnvironmentName}\": \"{CommonKeys.ValidEnvironmentId}\""; - SetConfigValue("project-id", CommonKeys.ValidProjectId); await GetLoggedInCli() .Command("env list") - .AssertStandardOutputContains(expectedReturn) + .AssertStandardOutput( + output => + { + StringAssert.Contains(CommonKeys.ValidEnvironmentName, output); + StringAssert.Contains(CommonKeys.ValidEnvironmentId, output); + }) .ExecuteAsync(); } diff --git a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Unity.Services.Cli.IntegrationTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Unity.Services.Cli.IntegrationTest.csproj index 10fb01b..85dbb7e 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Unity.Services.Cli.IntegrationTest.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.IntegrationTest/Unity.Services.Cli.IntegrationTest.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable true false @@ -18,7 +18,7 @@ - + diff --git a/Unity.Services.Cli/Unity.Services.Cli.Leaderboards.UnitTest/Unity.Services.Cli.Leaderboards.UnitTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.Leaderboards.UnitTest/Unity.Services.Cli.Leaderboards.UnitTest.csproj index 0bf54db..0b283d0 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Leaderboards.UnitTest/Unity.Services.Cli.Leaderboards.UnitTest.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Leaderboards.UnitTest/Unity.Services.Cli.Leaderboards.UnitTest.csproj @@ -1,7 +1,7 @@ Exe - net6.0 + net8.0 enable enable true @@ -16,15 +16,10 @@ - + - - - ..\Unity.Services.Cli\bin\Debug\net6.0\Unity.Services.Cli.Leaderboards.dll - - \ No newline at end of file diff --git a/Unity.Services.Cli/Unity.Services.Cli.Leaderboards/Deploy/LeaderboardsSerializer.cs b/Unity.Services.Cli/Unity.Services.Cli.Leaderboards/Deploy/LeaderboardsSerializer.cs index 502ebc4..2f157d3 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Leaderboards/Deploy/LeaderboardsSerializer.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Leaderboards/Deploy/LeaderboardsSerializer.cs @@ -1,3 +1,5 @@ +using System.Runtime.Serialization; +using Newtonsoft.Json; using Unity.Services.Leaderboards.Authoring.Core.Model; using Unity.Services.Leaderboards.Authoring.Core.Serialization; @@ -5,6 +7,73 @@ namespace Unity.Services.Cli.Leaderboards.Deploy; class LeaderboardsSerializer : ILeaderboardsSerializer { + enum ErrorMessage + { + ParsingError, + Error, + } + + static JsonSerializerSettings s_DefaultSerializerSettings = new JsonSerializerSettings() + { + // this option is necessary to prevent default values to be set instead of + // leaving them to null when absent from the json + DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, + + // this is a workaround to make sure this throws an exception in case there + // is extra characters at the end of the json + CheckAdditionalContent = true, + + // we want it to fail if an unknown field is present in the json + MissingMemberHandling = MissingMemberHandling.Error, + }; + + public LeaderboardConfig Deserialize(string path) + { + var lbcfg = new LeaderboardConfig(default); + DeserializeAndPopulate(lbcfg); + return lbcfg; + } + + public void DeserializeAndPopulate(LeaderboardConfig config) + { + if (string.IsNullOrEmpty(config.Path)) + { + throw new ArgumentNullException(nameof(config.Path), "impossible to deserialize an asset with empty path."); + } + if (config is null) + { + throw new ArgumentNullException(nameof(config)); + } + try + { + var content = File.ReadAllText(config.Path); + + var id = Path.GetFileNameWithoutExtension(config.Path); + + // PopulateObject() will append already existing tiers if FromJson() called more than once + // see https://www.newtonsoft.com/json/help/html/PopulateObject.htm + config.TieringConfig?.Tiers?.Clear(); + + JsonConvert.PopulateObject(content, config, s_DefaultSerializerSettings); + + // Overriding logic: + // 1. if Name is not set in JSON, name is same as Id + // 2. if Id is not set in JSON, Id is same as filename without extension + config.Id ??= id; + config.Name ??= config.Id; + } + catch (Exception e) when (e is SerializationException + or JsonSerializationException + or JsonReaderException) + { + throw new LeaderboardsDeserializeException(ErrorMessage.ParsingError.ToString(), e.Message, e); + } + catch (Exception e) + { + throw new LeaderboardsDeserializeException(ErrorMessage.Error.ToString(), e.Message, e); + } + } + public string Serialize(ILeaderboardConfig config) { var fileName = Path.GetFileNameWithoutExtension(config.Path); @@ -21,3 +90,16 @@ public string Serialize(ILeaderboardConfig config) return leaderboardFile.FileBodyText; } } + +class LeaderboardsDeserializeException : Exception +{ + public string ErrorMessage; + public string Details; + + public LeaderboardsDeserializeException(string message, string details, Exception exception) + : base(message, exception) + { + ErrorMessage = message; + Details = details; + } +} diff --git a/Unity.Services.Cli/Unity.Services.Cli.Leaderboards/Unity.Services.Cli.Leaderboards.csproj b/Unity.Services.Cli/Unity.Services.Cli.Leaderboards/Unity.Services.Cli.Leaderboards.csproj index 7427d12..92e97a0 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Leaderboards/Unity.Services.Cli.Leaderboards.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Leaderboards/Unity.Services.Cli.Leaderboards.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable enable true @@ -29,7 +29,7 @@ - + \ No newline at end of file diff --git a/Unity.Services.Cli/Unity.Services.Cli.Lobby.UnitTest/Unity.Services.Cli.Lobby.UnitTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.Lobby.UnitTest/Unity.Services.Cli.Lobby.UnitTest.csproj index d3425c1..eab63b7 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Lobby.UnitTest/Unity.Services.Cli.Lobby.UnitTest.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Lobby.UnitTest/Unity.Services.Cli.Lobby.UnitTest.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable enable true diff --git a/Unity.Services.Cli/Unity.Services.Cli.Lobby/Unity.Services.Cli.Lobby.csproj b/Unity.Services.Cli/Unity.Services.Cli.Lobby/Unity.Services.Cli.Lobby.csproj index 28ac3f3..9598bb9 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Lobby/Unity.Services.Cli.Lobby.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Lobby/Unity.Services.Cli.Lobby.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 10 enable enable diff --git a/Unity.Services.Cli/Unity.Services.Cli.Player.UnitTest/Unity.Services.Cli.Player.UnitTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.Player.UnitTest/Unity.Services.Cli.Player.UnitTest.csproj index 25ec4e6..3d72700 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Player.UnitTest/Unity.Services.Cli.Player.UnitTest.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Player.UnitTest/Unity.Services.Cli.Player.UnitTest.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable enable false diff --git a/Unity.Services.Cli/Unity.Services.Cli.Player/Unity.Services.Cli.Player.csproj b/Unity.Services.Cli/Unity.Services.Cli.Player/Unity.Services.Cli.Player.csproj index b75e1a6..02197a3 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Player/Unity.Services.Cli.Player.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Player/Unity.Services.Cli.Player.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 10 enable enable diff --git a/Unity.Services.Cli/Unity.Services.Cli.RemoteConfig.UnitTest/Unity.Services.Cli.RemoteConfig.UnitTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.RemoteConfig.UnitTest/Unity.Services.Cli.RemoteConfig.UnitTest.csproj index 0bdae29..4ec5e6d 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.RemoteConfig.UnitTest/Unity.Services.Cli.RemoteConfig.UnitTest.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.RemoteConfig.UnitTest/Unity.Services.Cli.RemoteConfig.UnitTest.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable enable false diff --git a/Unity.Services.Cli/Unity.Services.Cli.RemoteConfig/Exceptions/ApiException.cs b/Unity.Services.Cli/Unity.Services.Cli.RemoteConfig/Exceptions/ApiException.cs index c668506..fdcc839 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.RemoteConfig/Exceptions/ApiException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.RemoteConfig/Exceptions/ApiException.cs @@ -5,9 +5,6 @@ namespace Unity.Services.Cli.RemoteConfig.Exceptions; public class ApiException : CliException { - protected ApiException(SerializationInfo info, StreamingContext context) - : base(info, context) { } - public ApiException(string message, Exception? innerException, int exitCode) : base(message, innerException, exitCode) { } diff --git a/Unity.Services.Cli/Unity.Services.Cli.RemoteConfig/Unity.Services.Cli.RemoteConfig.csproj b/Unity.Services.Cli/Unity.Services.Cli.RemoteConfig/Unity.Services.Cli.RemoteConfig.csproj index 0d85824..7ffe9e3 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.RemoteConfig/Unity.Services.Cli.RemoteConfig.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.RemoteConfig/Unity.Services.Cli.RemoteConfig.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 10 enable enable diff --git a/Unity.Services.Cli/Unity.Services.Cli.Scheduler.UnitTest/Deploy/SchedulerDeploymentHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Scheduler.UnitTest/Deploy/SchedulerDeploymentHandlerTests.cs index dfe1293..798fcf0 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Scheduler.UnitTest/Deploy/SchedulerDeploymentHandlerTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Scheduler.UnitTest/Deploy/SchedulerDeploymentHandlerTests.cs @@ -111,16 +111,16 @@ public async Task DeployAsync_StatusesSet() It.Is(l => l.Id == "foo")), Times.Once); var expectedCreatedSchedule = actualRes.Deployed.FirstOrDefault(l => l.Id == "bar"); - Assert.IsTrue(expectedCreatedSchedule.Status.Message == "Deployed"); - Assert.IsTrue(expectedCreatedSchedule.Status.MessageDetail == "Created"); + Assert.IsTrue(expectedCreatedSchedule?.Status.Message == "Deployed"); + Assert.IsTrue(expectedCreatedSchedule?.Status.MessageDetail == "Created"); var expectedUpdatedSchedule = actualRes.Deployed.FirstOrDefault(l => l.Id == "foo"); - Assert.IsTrue(expectedUpdatedSchedule.Status.Message == "Deployed"); - Assert.IsTrue(expectedUpdatedSchedule.Status.MessageDetail == "Updated"); + Assert.IsTrue(expectedUpdatedSchedule?.Status.Message == "Deployed"); + Assert.IsTrue(expectedUpdatedSchedule?.Status.MessageDetail == "Updated"); var expectedDeletedSchedule = actualRes.Deployed.FirstOrDefault(l => l.Id == "echo"); - Assert.IsTrue(expectedDeletedSchedule.Status.Message == "Deployed"); - Assert.IsTrue(expectedDeletedSchedule.Status.MessageDetail == "Deleted"); + Assert.IsTrue(expectedDeletedSchedule?.Status.Message == "Deployed"); + Assert.IsTrue(expectedDeletedSchedule?.Status.MessageDetail == "Deleted"); } [Test] diff --git a/Unity.Services.Cli/Unity.Services.Cli.Scheduler.UnitTest/Deploy/SchedulerFetchHandlerTests.cs b/Unity.Services.Cli/Unity.Services.Cli.Scheduler.UnitTest/Deploy/SchedulerFetchHandlerTests.cs index d37a439..efa10b2 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Scheduler.UnitTest/Deploy/SchedulerFetchHandlerTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Scheduler.UnitTest/Deploy/SchedulerFetchHandlerTests.cs @@ -157,16 +157,16 @@ public async Task FetchAsync_StatusesAreCorrect() ); var expectedCreatedSchedule = actualRes.Fetched.FirstOrDefault(l => l.Name == "schedule4"); - Assert.IsTrue(expectedCreatedSchedule.Status.Message == "Fetched"); - Assert.IsTrue(expectedCreatedSchedule.Status.MessageDetail == "Created"); + Assert.IsTrue(expectedCreatedSchedule?.Status.Message == "Fetched"); + Assert.IsTrue(expectedCreatedSchedule?.Status.MessageDetail == "Created"); var expectedUpdatedSchedule = actualRes.Fetched.FirstOrDefault(l => l.Name == "schedule1"); - Assert.IsTrue(expectedUpdatedSchedule.Status.Message == "Fetched"); - Assert.IsTrue(expectedUpdatedSchedule.Status.MessageDetail == "Updated"); + Assert.IsTrue(expectedUpdatedSchedule?.Status.Message == "Fetched"); + Assert.IsTrue(expectedUpdatedSchedule?.Status.MessageDetail == "Updated"); var expectedDeletedSchedule = actualRes.Fetched.FirstOrDefault(l => l.Name == "schedule2"); - Assert.IsTrue(expectedDeletedSchedule.Status.Message == "Fetched"); - Assert.IsTrue(expectedDeletedSchedule.Status.MessageDetail == "Deleted"); + Assert.IsTrue(expectedDeletedSchedule?.Status.Message == "Fetched"); + Assert.IsTrue(expectedDeletedSchedule?.Status.MessageDetail == "Deleted"); } @@ -214,7 +214,8 @@ public async Task FetchAsync_DuplicateNames() "recurring", "0 * * * *", 1, - "{}") { Path = "otherpath"}); + "{}") + { Path = "otherpath" }); var remoteSchedules = GetRemoteConfigs(); Mock mockSchedulerClient = new(); diff --git a/Unity.Services.Cli/Unity.Services.Cli.Scheduler.UnitTest/Unity.Services.Cli.Scheduler.UnitTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.Scheduler.UnitTest/Unity.Services.Cli.Scheduler.UnitTest.csproj index 0145693..0fa9422 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Scheduler.UnitTest/Unity.Services.Cli.Scheduler.UnitTest.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Scheduler.UnitTest/Unity.Services.Cli.Scheduler.UnitTest.csproj @@ -1,8 +1,9 @@ - net6.0 + net8.0 enable enable + true false diff --git a/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Deploy/SchedulerClient.cs b/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Deploy/SchedulerClient.cs index 79d2e09..8b6cf99 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Deploy/SchedulerClient.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Deploy/SchedulerClient.cs @@ -14,14 +14,14 @@ namespace Unity.Services.Cli.Scheduler.Deploy; class SchedulerClient : ISchedulerClient { readonly ISchedulerApiAsync m_SchedulerApi; - readonly IServiceAccountAuthenticationService m_AuthenticationService; + readonly IServiceAccountAuthenticationService m_AuthenticationService; readonly IConfigurationValidator m_Validator; internal Guid ProjectId { get; set; } internal Guid EnvironmentId { get; set; } internal CancellationToken CancellationToken { get; set; } public SchedulerClient(ISchedulerApiAsync schedulerApi, - IServiceAccountAuthenticationService authenticationService, + IServiceAccountAuthenticationService authenticationService, IConfigurationValidator validator) { m_SchedulerApi = schedulerApi; diff --git a/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Deploy/SchedulerDeployFetchBase.cs b/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Deploy/SchedulerDeployFetchBase.cs index 742dd81..d44474e 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Deploy/SchedulerDeployFetchBase.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Deploy/SchedulerDeployFetchBase.cs @@ -45,12 +45,12 @@ protected static void SetFileStatus(IReadOnlyList deserialized } } - protected async Task<(IReadOnlyList,IReadOnlyList)> GetResourcesFromFiles( + protected async Task<(IReadOnlyList, IReadOnlyList)> GetResourcesFromFiles( IReadOnlyCollection filePaths, CancellationToken token) { - var resources = await Task.WhenAll( - filePaths.Select(f => m_ResourceLoader.LoadResource(f,token))); + var resources = await Task.WhenAll( + filePaths.Select(f => m_ResourceLoader.LoadResource(f, token))); var deserializedFiles = resources .Where(r => r.Status.MessageSeverity != SeverityLevel.Error) .ToList(); diff --git a/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Exceptions/SchedulerException.cs b/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Exceptions/SchedulerException.cs index 977e499..5a87065 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Exceptions/SchedulerException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Exceptions/SchedulerException.cs @@ -6,11 +6,8 @@ namespace Unity.Services.Cli.Scheduler.Exceptions; /// /// Example of custom exception for incorrect user operation. /// -[Serializable] public class SchedulerException : CliException { - protected SchedulerException(SerializationInfo info, StreamingContext context) : base(info, context) { } - public SchedulerException(int exitCode = Common.Exceptions.ExitCode.HandledError) : base(exitCode) { } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Unity.Services.Cli.Scheduler.csproj b/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Unity.Services.Cli.Scheduler.csproj index 2839dd2..cc29b41 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Unity.Services.Cli.Scheduler.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Scheduler/Unity.Services.Cli.Scheduler.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 10 enable enable diff --git a/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest/AuthenticationModuleTests.cs b/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest/AuthenticationModuleTests.cs index fdfd878..f345ecd 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest/AuthenticationModuleTests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest/AuthenticationModuleTests.cs @@ -39,7 +39,7 @@ public void LoginCommandContainsExpectedInput() public void RegistersServicesRegisteredExpectedService(Type serviceType) { var collection = new ServiceCollection(); - collection.AddSingleton(new Mock().Object); + collection.AddSingleton(new Mock().Object); var context = new HostBuilderContext(new Dictionary()); AuthenticationModule.RegisterServices(context, collection); diff --git a/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest/Authenticator/AuthenticatorV1Tests.cs b/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest/Authenticator/AuthenticatorV1Tests.cs index 50c4ec3..e018467 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest/Authenticator/AuthenticatorV1Tests.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest/Authenticator/AuthenticatorV1Tests.cs @@ -19,7 +19,7 @@ class AuthenticatorV1Tests static readonly string k_AccessToken = Convert.ToBase64String( Encoding.UTF8.GetBytes($"{k_ValidServiceKeyId}:{k_ValidServiceSecretKey}")); - readonly Mock m_MockPrompt = new(); + readonly Mock m_MockPrompt = new(); readonly Mock> m_MockPersister = new(); TextReader? m_PreviousConsoleInput; @@ -54,8 +54,8 @@ public async Task LoginAsyncWithoutArgumentPromptsAndPersistsExpectedToken() .ReturnsAsync(k_ValidServiceKeyId); m_MockPrompt.Setup(p => p.PromptAsync(AuthenticatorV1.SecretKeyPrompt, CancellationToken.None)) .ReturnsAsync(k_ValidServiceSecretKey); - m_MockPrompt.Setup(p => p.IsStandardInputRedirected) - .Returns(false); + m_MockPrompt.Setup(p => p.InteractiveEnabled) + .Returns(true); var authenticatorV1 = new AuthenticatorV1(m_MockPersister.Object, m_MockPrompt.Object); @@ -68,8 +68,8 @@ public async Task LoginAsyncWithoutArgumentPromptsAndPersistsExpectedToken() public void LoginAsyncWithoutArgumentAndRedirectedStandardInputThrows() { var input = new LoginInput(); - m_MockPrompt.Setup(p => p.IsStandardInputRedirected) - .Returns(true); + m_MockPrompt.Setup(p => p.InteractiveEnabled) + .Returns(false); var authenticatorV1 = new AuthenticatorV1(m_MockPersister.Object, m_MockPrompt.Object); diff --git a/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest.csproj index 3fc55ac..bba3b0a 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest/Unity.Services.Cli.ServiceAccountAuthentication.UnitTest.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable enable Unity.Services.Cli.Authentication.UnitTest diff --git a/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication/AuthenticationModule.cs b/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication/AuthenticationModule.cs index a925a25..accc71c 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication/AuthenticationModule.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication/AuthenticationModule.cs @@ -55,7 +55,7 @@ public static void RegisterServices(HostBuilderContext hostBuilderContext, IServ var persister = new JsonFilePersister(credentialsPath); var environmentProvider = new SystemEnvironmentProvider(); var serviceProvider = serviceCollection.BuildServiceProvider(); - var cliPrompt = serviceProvider.GetRequiredService(); + var cliPrompt = serviceProvider.GetRequiredService(); var authenticator = new AuthenticatorV1(persister, cliPrompt); serviceCollection.AddSingleton(authenticator); diff --git a/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication/Authenticator/AuthenticatorV1.cs b/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication/Authenticator/AuthenticatorV1.cs index ace6fe9..f6ec197 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication/Authenticator/AuthenticatorV1.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication/Authenticator/AuthenticatorV1.cs @@ -25,9 +25,9 @@ internal const string EnvironmentVariablesAndConfigSetWarning .Validate(s => s.Any(char.IsWhiteSpace) ? ValidationResult.Error("secret-key should not contain white space.") : ValidationResult.Success()); readonly IPersister m_Persister; - readonly ICliPrompt m_CliPrompt; + readonly IConsolePrompt m_CliPrompt; - public AuthenticatorV1(IPersister persister, ICliPrompt cliPrompt) + public AuthenticatorV1(IPersister persister, IConsolePrompt cliPrompt) { m_Persister = persister; m_CliPrompt = cliPrompt; @@ -45,7 +45,7 @@ public async Task LoginAsync(LoginInput input, CancellationToken cancellationTok } else { - if (m_CliPrompt.IsStandardInputRedirected) + if (!m_CliPrompt.InteractiveEnabled) { throw new InvalidLoginInputException($"Standard Input is redirected, please use the " + $"\"{LoginInput.ServiceKeyIdAlias}\" and \"{LoginInput.ServiceSecretKeyAlias}\" options to login."); @@ -59,7 +59,7 @@ public async Task LoginAsync(LoginInput input, CancellationToken cancellationTok } internal static async Task<(string, string)> PromptForServiceAccountKeysAsync( - ICliPrompt cliPrompt, CancellationToken cancellationToken) + IConsolePrompt cliPrompt, CancellationToken cancellationToken) { var keyId = await cliPrompt.PromptAsync(KeyIdPrompt, cancellationToken); var secretKey = await cliPrompt.PromptAsync(SecretKeyPrompt, cancellationToken); diff --git a/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication/Unity.Services.Cli.ServiceAccountAuthentication.csproj b/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication/Unity.Services.Cli.ServiceAccountAuthentication.csproj index 8b48836..1b3fbcd 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication/Unity.Services.Cli.ServiceAccountAuthentication.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.ServiceAccountAuthentication/Unity.Services.Cli.ServiceAccountAuthentication.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable enable true diff --git a/Unity.Services.Cli/Unity.Services.Cli.TestUtils/MockHelper.cs b/Unity.Services.Cli/Unity.Services.Cli.TestUtils/MockHelper.cs index 625cd72..784fe08 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.TestUtils/MockHelper.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.TestUtils/MockHelper.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.Logging; using Moq; using Unity.Services.Cli.Common; -using Unity.Services.Cli.Common.Telemetry; +using Unity.Services.Cli.Common.Console; using Unity.Services.Cli.Common.Telemetry.AnalyticEvent; using Unity.Services.Cli.Environment; @@ -11,14 +11,16 @@ public class MockHelper { public readonly Mock MockConfiguration = new(); public readonly Mock MockEnvironment = new(); + public readonly Mock MockConsoleTable = new(); public readonly Mock MockLogger = new(); public readonly Mock MockDiagnostics = new(); public void ClearInvocations() { - MockConfiguration.Invocations.Clear(); - MockEnvironment.Invocations.Clear(); - MockLogger.Invocations.Clear(); - MockDiagnostics.Invocations.Clear(); + MockConfiguration.Reset(); + MockEnvironment.Reset(); + MockLogger.Reset(); + MockDiagnostics.Reset(); + MockConsoleTable.Reset(); } } diff --git a/Unity.Services.Cli/Unity.Services.Cli.TestUtils/Unity.Services.Cli.TestUtils.csproj b/Unity.Services.Cli/Unity.Services.Cli.TestUtils/Unity.Services.Cli.TestUtils.csproj index 9b955b5..2d56650 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.TestUtils/Unity.Services.Cli.TestUtils.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.TestUtils/Unity.Services.Cli.TestUtils.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable enable true diff --git a/Unity.Services.Cli/Unity.Services.Cli.Triggers.UnitTest/Unity.Services.Cli.Triggers.UnitTest.csproj b/Unity.Services.Cli/Unity.Services.Cli.Triggers.UnitTest/Unity.Services.Cli.Triggers.UnitTest.csproj index 85c9299..a810de9 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Triggers.UnitTest/Unity.Services.Cli.Triggers.UnitTest.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Triggers.UnitTest/Unity.Services.Cli.Triggers.UnitTest.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 enable enable Unity.Services.Cli.Triggers.UnitTest diff --git a/Unity.Services.Cli/Unity.Services.Cli.Triggers/Exceptions/TriggersException.cs b/Unity.Services.Cli/Unity.Services.Cli.Triggers/Exceptions/TriggersException.cs index 5620f45..f7c0c0f 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Triggers/Exceptions/TriggersException.cs +++ b/Unity.Services.Cli/Unity.Services.Cli.Triggers/Exceptions/TriggersException.cs @@ -3,11 +3,8 @@ namespace Unity.Services.Cli.Triggers.Exceptions; -[Serializable] public class TriggersException : CliException { - protected TriggersException(SerializationInfo info, StreamingContext context) : base(info, context) { } - public TriggersException(int exitCode = Common.Exceptions.ExitCode.HandledError) : base(exitCode) { } diff --git a/Unity.Services.Cli/Unity.Services.Cli.Triggers/Unity.Services.Cli.Triggers.csproj b/Unity.Services.Cli/Unity.Services.Cli.Triggers/Unity.Services.Cli.Triggers.csproj index dc3e9e6..6c17ce3 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.Triggers/Unity.Services.Cli.Triggers.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli.Triggers/Unity.Services.Cli.Triggers.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 10 enable enable diff --git a/Unity.Services.Cli/Unity.Services.Cli.sln b/Unity.Services.Cli/Unity.Services.Cli.sln index defc8c2..0c99f09 100644 --- a/Unity.Services.Cli/Unity.Services.Cli.sln +++ b/Unity.Services.Cli/Unity.Services.Cli.sln @@ -43,7 +43,6 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Services.Cli.Leaderboards.UnitTest", "Unity.Services.Cli.Leaderboards.UnitTest\Unity.Services.Cli.Leaderboards.UnitTest.csproj", "{64C6D8E5-4396-419A-94F6-2B221E9B7860}" EndProject EndProject -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Services.Cli.Authoring", "Unity.Services.Cli.Authoring\Unity.Services.Cli.Authoring.csproj", "{43B27EA4-45B0-4346-BBE9-920D090CF477}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Services.Cli.Authoring.UnitTest", "Unity.Services.Cli.Authoring.UnitTest\Unity.Services.Cli.Authoring.UnitTest.csproj", "{55A7BF2B-3919-45A7-9B6F-7EAF6A81B047}" @@ -68,20 +67,22 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentat EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Services.Cli.Integration.MockServerApp", "Unity.Services.Cli.Integration.MockServerApp\Unity.Services.Cli.Integration.MockServerApp.csproj", "{27BBE31C-43CE-45AA-9DE4-BF3D097DC1A3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Services.Cli.CloudContentDelivery", "Unity.Services.Cli.CloudContentDelivery\Unity.Services.Cli.CloudContentDelivery.csproj", "{88AAA89D-F8E5-46CF-B1A8-E8B5AEB00D07}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Services.CloudContentDelivery.Authoring.Core", "Unity.Services.CloudContentDelivery.Authoring.Core\Unity.Services.CloudContentDelivery.Authoring.Core.csproj", "{63F6A8B5-EAA7-462C-B9F3-B1AEADC09933}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Configuration", "Configuration", "{ECED14B5-7FD3-4C82-8417-3C6E4A0FC054}" ProjectSection(SolutionItems) = preProject features-definition.json = features-definition.json EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Services.Access.Authoring.Core", "Unity.Services.Access.Authoring.Core\Unity.Services.Access.Authoring.Core.csproj", "{E37321BA-9520-46D7-A775-E3C834468B55}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Services.Cli.Triggers", "Unity.Services.Cli.Triggers\Unity.Services.Cli.Triggers.csproj", "{C46FA417-CF83-4DBB-8B31-1C6156FB0D4D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Services.Triggers.Authoring.Core", "Unity.Services.Triggers.Authoring.Core\Unity.Services.Triggers.Authoring.Core.csproj", "{919387DC-82F4-4DF9-A8EC-E32CED4B3447}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Services.Cli.Triggers.UnitTest", "Unity.Services.Cli.Triggers.UnitTest\Unity.Services.Cli.Triggers.UnitTest.csproj", "{BA3B69BA-FEF9-4F71-A73F-B4BE717CC392}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Services.Cli.CloudContentDelivery.UnitTest", "Unity.Services.Cli.CloudContentDelivery.UnitTest\Unity.Services.Cli.CloudContentDelivery.UnitTest.csproj", "{025F0D8D-97A2-4698-87FA-7FB40FF1E515}" EndProject EndProject EndProject @@ -92,6 +93,49 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Services.Scheduler.Au EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Services.Cli.Scheduler.UnitTest", "Unity.Services.Cli.Scheduler.UnitTest\Unity.Services.Cli.Scheduler.UnitTest.csproj", "{D511207A-820C-4AA9-AF46-D76F1027F105}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Latest", "Latest", "{210148BF-92DB-4EC2-9C1D-DB10E8ED7BB4}" +EndProject +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Badges", "Badges", "{2BA93646-8046-4B05-BB54-39D3F8975DFA}" + ProjectSection(SolutionItems) = preProject + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Badges\create.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Badges\create.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Badges\delete.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Badges\delete.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Badges\list.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Badges\list.mdx + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Buckets", "Buckets", "{F63CC5ED-B3B8-4ACF-BB5F-72FAE0850134}" + ProjectSection(SolutionItems) = preProject + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Buckets\create.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Buckets\create.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Buckets\delete.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Buckets\delete.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Buckets\info.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Buckets\info.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Buckets\list.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Buckets\list.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Buckets\permissions-update.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Buckets\permissions-update.mdx + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Releases", "Releases", "{0FAACE60-269C-4269-939A-D7FB022276AE}" + ProjectSection(SolutionItems) = preProject + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Releases\create.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Releases\create.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Releases\info.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Releases\info.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Releases\list.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Releases\list.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Releases\promote.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Releases\promote.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Releases\Promotions\status.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Releases\Promotions\status.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Releases\update.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Releases\update.mdx + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Entries", "Entries", "{43EC69B3-778B-441D-82A8-1868530E2B15}" + ProjectSection(SolutionItems) = preProject + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Entries\copy.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Entries\copy.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Entries\delete.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Entries\delete.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Entries\download.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Entries\download.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Entries\info.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Entries\info.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Entries\list.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Entries\list.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Entries\sync.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Entries\sync.mdx + ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Entries\update.mdx = ..\docs\ugs-cli\latest\cloud-content-delivery\Cloud Content Delivery Command Line\Commands\Entries\update.mdx + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CCD", "CCD", "{44AEB9D3-CDE4-4C0C-AE4E-07605839E89D}" +EndProject +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -216,10 +260,14 @@ Global {27BBE31C-43CE-45AA-9DE4-BF3D097DC1A3}.Debug|Any CPU.Build.0 = Debug|Any CPU {27BBE31C-43CE-45AA-9DE4-BF3D097DC1A3}.Release|Any CPU.ActiveCfg = Release|Any CPU {27BBE31C-43CE-45AA-9DE4-BF3D097DC1A3}.Release|Any CPU.Build.0 = Release|Any CPU - {E37321BA-9520-46D7-A775-E3C834468B55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E37321BA-9520-46D7-A775-E3C834468B55}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E37321BA-9520-46D7-A775-E3C834468B55}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E37321BA-9520-46D7-A775-E3C834468B55}.Release|Any CPU.Build.0 = Release|Any CPU + {88AAA89D-F8E5-46CF-B1A8-E8B5AEB00D07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88AAA89D-F8E5-46CF-B1A8-E8B5AEB00D07}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88AAA89D-F8E5-46CF-B1A8-E8B5AEB00D07}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88AAA89D-F8E5-46CF-B1A8-E8B5AEB00D07}.Release|Any CPU.Build.0 = Release|Any CPU + {63F6A8B5-EAA7-462C-B9F3-B1AEADC09933}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63F6A8B5-EAA7-462C-B9F3-B1AEADC09933}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63F6A8B5-EAA7-462C-B9F3-B1AEADC09933}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63F6A8B5-EAA7-462C-B9F3-B1AEADC09933}.Release|Any CPU.Build.0 = Release|Any CPU {829A8ADF-6608-48B2-B7E5-47DFADB991ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {829A8ADF-6608-48B2-B7E5-47DFADB991ED}.Debug|Any CPU.Build.0 = Debug|Any CPU {829A8ADF-6608-48B2-B7E5-47DFADB991ED}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -240,6 +288,10 @@ Global {D8E319B8-E411-4A68-B2EA-39D3B65C745C}.Debug|Any CPU.Build.0 = Debug|Any CPU {D8E319B8-E411-4A68-B2EA-39D3B65C745C}.Release|Any CPU.ActiveCfg = Release|Any CPU {D8E319B8-E411-4A68-B2EA-39D3B65C745C}.Release|Any CPU.Build.0 = Release|Any CPU + {025F0D8D-97A2-4698-87FA-7FB40FF1E515}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {025F0D8D-97A2-4698-87FA-7FB40FF1E515}.Debug|Any CPU.Build.0 = Debug|Any CPU + {025F0D8D-97A2-4698-87FA-7FB40FF1E515}.Release|Any CPU.ActiveCfg = Release|Any CPU + {025F0D8D-97A2-4698-87FA-7FB40FF1E515}.Release|Any CPU.Build.0 = Release|Any CPU {7BA75DA5-852E-4B13-AA6A-C79B7E268418}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7BA75DA5-852E-4B13-AA6A-C79B7E268418}.Debug|Any CPU.Build.0 = Debug|Any CPU {7BA75DA5-852E-4B13-AA6A-C79B7E268418}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -252,5 +304,16 @@ Global {D511207A-820C-4AA9-AF46-D76F1027F105}.Debug|Any CPU.Build.0 = Debug|Any CPU {D511207A-820C-4AA9-AF46-D76F1027F105}.Release|Any CPU.ActiveCfg = Release|Any CPU {D511207A-820C-4AA9-AF46-D76F1027F105}.Release|Any CPU.Build.0 = Release|Any CPU + {77576293-7E51-4A9C-8DDA-F2D75C8C59ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77576293-7E51-4A9C-8DDA-F2D75C8C59ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77576293-7E51-4A9C-8DDA-F2D75C8C59ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77576293-7E51-4A9C-8DDA-F2D75C8C59ED}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {44AEB9D3-CDE4-4C0C-AE4E-07605839E89D} = {210148BF-92DB-4EC2-9C1D-DB10E8ED7BB4} + {43EC69B3-778B-441D-82A8-1868530E2B15} = {44AEB9D3-CDE4-4C0C-AE4E-07605839E89D} + {0FAACE60-269C-4269-939A-D7FB022276AE} = {44AEB9D3-CDE4-4C0C-AE4E-07605839E89D} + {F63CC5ED-B3B8-4ACF-BB5F-72FAE0850134} = {44AEB9D3-CDE4-4C0C-AE4E-07605839E89D} + {2BA93646-8046-4B05-BB54-39D3F8975DFA} = {44AEB9D3-CDE4-4C0C-AE4E-07605839E89D} EndGlobalSection EndGlobal diff --git a/Unity.Services.Cli/Unity.Services.Cli/Program.cs b/Unity.Services.Cli/Unity.Services.Cli/Program.cs index bf08a3c..981120e 100644 --- a/Unity.Services.Cli/Unity.Services.Cli/Program.cs +++ b/Unity.Services.Cli/Unity.Services.Cli/Program.cs @@ -39,6 +39,9 @@ using Unity.Services.Cli.Access; using Unity.Services.Cli.Scheduler; +using Unity.Services.Cli.CloudContentDelivery; + + namespace Unity.Services.Cli; public static partial class Program @@ -89,6 +92,8 @@ public static async Task InternalMain(string[] args, Logger logger) #endif host.ConfigureServices(LeaderboardsModule.RegisterServices); host.ConfigureServices(PlayerModule.RegisterServices); + + host.ConfigureServices(CloudContentDeliveryModule.RegisterServices); host.ConfigureServices(serviceCollection => serviceCollection .AddSingleton(systemEnvironmentProvider)); @@ -162,6 +167,8 @@ public static async Task InternalMain(string[] args, Logger logger) .AddModule(new GameServerHostingModule()) .AddModule(new PlayerModule()) .AddModule(new RemoteConfigModule()) + + .AddModule(new CloudContentDeliveryModule()) .Build(); return await parser diff --git a/Unity.Services.Cli/Unity.Services.Cli/Unity.Services.Cli.csproj b/Unity.Services.Cli/Unity.Services.Cli/Unity.Services.Cli.csproj index a8096c1..6835e6e 100644 --- a/Unity.Services.Cli/Unity.Services.Cli/Unity.Services.Cli.csproj +++ b/Unity.Services.Cli/Unity.Services.Cli/Unity.Services.Cli.csproj @@ -1,10 +1,10 @@ Exe - net6.0 + net8.0 10 ugs - 1.3.0 + 1.4.0 true true @@ -18,9 +18,10 @@ - + + diff --git a/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Deploy/CloudContentDeliveryDeploymentHandler.cs b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Deploy/CloudContentDeliveryDeploymentHandler.cs new file mode 100644 index 0000000..6f94a57 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Deploy/CloudContentDeliveryDeploymentHandler.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Unity.Services.DeploymentApi.Editor; +using Unity.Services.CloudContentDelivery.Authoring.Core.Model; +using Unity.Services.CloudContentDelivery.Authoring.Core.Service; +using Unity.Services.CloudContentDelivery.Authoring.Core.Validations; + +namespace Unity.Services.CloudContentDelivery.Authoring.Core.Deploy +{ + public class CloudContentDeliveryDeploymentHandler : ICloudContentDeliveryDeploymentHandler + { + readonly ICloudContentDeliveryClient m_Client; + + public CloudContentDeliveryDeploymentHandler(ICloudContentDeliveryClient client) + { + m_Client = client; + } + + public async Task DeployAsync( + IReadOnlyList localResources, + bool dryRun = false, + bool reconcile = false, + CancellationToken token = default) + { + var res = new DeployResult(); + + localResources = DuplicateResourceValidation.FilterDuplicateResources( + localResources, out var duplicateGroups); + + var remoteResources = await m_Client.List(); + + var toCreate = localResources + .Except(remoteResources) + .ToList(); + + var toUpdate = localResources + .Except(toCreate) + .ToList(); + + var toDelete = new List(); + if (reconcile) + { + toDelete = remoteResources + .Except(localResources) + .ToList(); + } + + res.Created = toCreate; + res.Deleted = toDelete; + res.Updated = toUpdate; + res.Deployed = new List(); + res.Failed = new List(); + + UpdateDuplicateResourceStatus(res, duplicateGroups, dryRun); + + if (dryRun) + { + return res; + } + + var createTasks = new List<(IBucket, Task)>(); + var updateTasks = new List<(IBucket, Task)>(); + var deleteTasks = new List<(IBucket, Task)>(); + + foreach (var localResource in toCreate) + { + createTasks.Add((localResource, m_Client.Create(localResource))); + } + + foreach (var resource in toUpdate) + { + updateTasks.Add((resource, m_Client.Update(resource))); + } + + if (reconcile) + { + foreach (var resource in toDelete) + { + deleteTasks.Add((resource, m_Client.Delete(resource))); + } + + } + + await UpdateResult(createTasks, res); + await UpdateResult(updateTasks, res); + await UpdateResult(deleteTasks, res); + + return res; + } + + protected virtual void UpdateStatus( + IBucket bucket, + DeploymentStatus status) + { + // clients can override this to provide user feedback on progress + bucket.Status = status; + } + + protected virtual void UpdateProgress( + IBucket bucket, + float progress) + { + // clients can override this to provide user feedback on progress + bucket.Progress = progress; + } + + void UpdateDuplicateResourceStatus( + DeployResult result, + IReadOnlyList> duplicateGroups, + bool dryRun) + { + foreach (var group in duplicateGroups) + { + foreach (var res in group) + { + result.Failed.Add(res); + var (message, shortMessage) = DuplicateResourceValidation.GetDuplicateResourceErrorMessages(res, group.ToList()); + UpdateStatus(res, Statuses.GetFailedToDeploy(shortMessage)); + } + } + } + + async Task UpdateResult( + List<(IBucket, Task)> tasks, + DeployResult res) + { + foreach (var (resource, task) in tasks) + { + try + { + await task; + res.Deployed.Add(resource); + UpdateStatus(resource, Statuses.Deployed); + UpdateProgress(resource, 100); + } + catch (Exception e) + { + res.Failed.Add(resource); + + UpdateStatus(resource, Statuses.GetFailedToDeploy(e.Message)); + } + } + } + } +} diff --git a/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Deploy/DeployResult.cs b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Deploy/DeployResult.cs new file mode 100644 index 0000000..8926c35 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Deploy/DeployResult.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Unity.Services.CloudContentDelivery.Authoring.Core.Model; + +namespace Unity.Services.CloudContentDelivery.Authoring.Core.Deploy +{ + public class DeployResult + { + public List Created { get; set; } + public List Updated { get; set; } + public List Deleted { get; set; } + public List Deployed { get; set; } + public List Failed { get; set; } + } +} diff --git a/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Deploy/ICloudContentDeliveryDeploymentHandler.cs b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Deploy/ICloudContentDeliveryDeploymentHandler.cs new file mode 100644 index 0000000..7324ab2 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Deploy/ICloudContentDeliveryDeploymentHandler.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Unity.Services.CloudContentDelivery.Authoring.Core.Model; + +namespace Unity.Services.CloudContentDelivery.Authoring.Core.Deploy +{ + public interface ICloudContentDeliveryDeploymentHandler + { + Task DeployAsync(IReadOnlyList localResources, + bool dryRun = false, + bool reconcile = false, + CancellationToken token = default); + } +} diff --git a/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Fetch/CloudContentDeliveryFetchHandler.cs b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Fetch/CloudContentDeliveryFetchHandler.cs new file mode 100644 index 0000000..76abd05 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Fetch/CloudContentDeliveryFetchHandler.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Unity.Services.DeploymentApi.Editor; +using Unity.Services.CloudContentDelivery.Authoring.Core.IO; +using Unity.Services.CloudContentDelivery.Authoring.Core.Model; +using Unity.Services.CloudContentDelivery.Authoring.Core.Service; +using Unity.Services.CloudContentDelivery.Authoring.Core.Validations; + +namespace Unity.Services.CloudContentDelivery.Authoring.Core.Fetch +{ + public class CloudContentDeliveryFetchHandler : ICloudContentDeliveryFetchHandler + { + readonly ICloudContentDeliveryClient m_Client; + readonly IFileSystem m_FileSystem; + + public CloudContentDeliveryFetchHandler( + ICloudContentDeliveryClient client, + IFileSystem fileSystem) + { + m_Client = client; + m_FileSystem = fileSystem; + } + + public async Task FetchAsync(string rootDirectory, + IReadOnlyList localResources, + bool dryRun = false, + bool reconcile = false, + CancellationToken token = default) + { + var res = new FetchResult(); + + localResources = DuplicateResourceValidation.FilterDuplicateResources( + localResources, out var duplicateGroups); + + var remoteResources = await m_Client.List(); + + var toUpdate = remoteResources + .Intersect(localResources) + .ToList(); + + var toDelete = localResources + .Except(remoteResources) + .ToList(); + + var toCreate = new List(); + if (reconcile) + { + toCreate = remoteResources + .Except(localResources) + .ToList(); + } + + res.Created = toCreate; + res.Deleted = toDelete; + res.Updated = toUpdate; + res.Fetched = new List(); + res.Failed = new List(); + + UpdateDuplicateResourceStatus(res, duplicateGroups, dryRun); + + if (dryRun) + { + return res; + } + + var updateTasks = new List<(IBucket, Task)>(); + var deleteTasks = new List<(IBucket, Task)>(); + var createTasks = new List<(IBucket, Task)>(); + + foreach (var resource in toUpdate) + { + var task = m_FileSystem.WriteAllText( + resource.Path, + res.ToString(), + token); + updateTasks.Add((resource, task)); + } + + foreach (var resource in toDelete) + { + var task = m_FileSystem.Delete( + resource.Path, + token); + deleteTasks.Add((resource, task)); + } + + if (reconcile) + { + foreach (var resource in toCreate) + { + var task = m_FileSystem.WriteAllText( + resource.Path, + resource.Name, + token); + createTasks.Add((resource, task)); + } + } + + await UpdateResult(updateTasks, res); + await UpdateResult(deleteTasks, res); + await UpdateResult(createTasks, res); + + return res; + } + + protected virtual void UpdateStatus( + IBucket bucket, + DeploymentStatus status) + { + // clients can override this to provide user feedback on progress + bucket.Status = status; + } + + protected virtual void UpdateProgress( + IBucket bucket, + float progress) + { + // clients can override this to provide user feedback on progress + bucket.Progress = progress; + } + + void UpdateDuplicateResourceStatus( + FetchResult result, + IReadOnlyList> duplicateGroups, + bool dryRun) + { + foreach (var group in duplicateGroups) + { + foreach (var res in group) + { + result.Failed.Add(res); + var (message, shortMessage) = DuplicateResourceValidation.GetDuplicateResourceErrorMessages(res, group.ToList()); + UpdateStatus(res, Statuses.GetFailedToFetch(shortMessage)); + } + } + } + + async Task UpdateResult( + List<(IBucket, Task)> tasks, + FetchResult res) + { + foreach (var (resource, task) in tasks) + { + try + { + await task; + res.Fetched.Add(resource); + UpdateStatus(resource, Statuses.Fetched); + UpdateProgress(resource, 100); + } + catch (Exception e) + { + res.Failed.Add(resource); + UpdateStatus(resource, Statuses.GetFailedToFetch(e.Message)); + } + } + } + } +} diff --git a/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Fetch/FetchResult.cs b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Fetch/FetchResult.cs new file mode 100644 index 0000000..6f5bb19 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Fetch/FetchResult.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Unity.Services.CloudContentDelivery.Authoring.Core.Model; + +namespace Unity.Services.CloudContentDelivery.Authoring.Core.Fetch +{ + public class FetchResult + { + public List Created { get; set; } + public List Updated { get; set; } + public List Deleted { get; set; } + public List Fetched { get; set; } + public List Failed { get; set; } + } +} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Fetch/IProjectAccessFetchHandler.cs b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Fetch/ICloudContentDeliveryFetchHandler.cs similarity index 54% rename from Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Fetch/IProjectAccessFetchHandler.cs rename to Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Fetch/ICloudContentDeliveryFetchHandler.cs index b5bc2db..9bee641 100644 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Fetch/IProjectAccessFetchHandler.cs +++ b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Fetch/ICloudContentDeliveryFetchHandler.cs @@ -1,16 +1,15 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Unity.Services.Access.Authoring.Core.Model; -using Unity.Services.Access.Authoring.Core.Results; +using Unity.Services.CloudContentDelivery.Authoring.Core.Model; -namespace Unity.Services.Access.Authoring.Core.Fetch +namespace Unity.Services.CloudContentDelivery.Authoring.Core.Fetch { - public interface IProjectAccessFetchHandler + public interface ICloudContentDeliveryFetchHandler { public Task FetchAsync( string rootDirectory, - IReadOnlyList files, + IReadOnlyList localResources, bool dryRun = false, bool reconcile = false, CancellationToken token = default); diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/IO/IFileSystem.cs b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/IO/IFileSystem.cs similarity index 87% rename from Unity.Services.Cli/Unity.Services.Access.Authoring.Core/IO/IFileSystem.cs rename to Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/IO/IFileSystem.cs index 2ea60ab..271433c 100644 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/IO/IFileSystem.cs +++ b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/IO/IFileSystem.cs @@ -1,7 +1,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Unity.Services.Access.Authoring.Core.IO +namespace Unity.Services.CloudContentDelivery.Authoring.Core.IO { public interface IFileSystem { diff --git a/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Model/IBucket.cs b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Model/IBucket.cs new file mode 100644 index 0000000..fc90ce2 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Model/IBucket.cs @@ -0,0 +1,10 @@ +using Unity.Services.DeploymentApi.Editor; + +namespace Unity.Services.CloudContentDelivery.Authoring.Core.Model +{ + public interface IBucket : IDeploymentItem, ITypedItem + { + string Id { get; } + new float Progress { get; set; } + } +} diff --git a/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Model/Statuses.cs b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Model/Statuses.cs new file mode 100644 index 0000000..d6c6211 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Model/Statuses.cs @@ -0,0 +1,20 @@ +using Unity.Services.DeploymentApi.Editor; + + +namespace Unity.Services.CloudContentDelivery.Authoring.Core.Model +{ + static class Statuses + { + public static readonly DeploymentStatus FailedToLoad = new("Failed to load", string.Empty, SeverityLevel.Error); + + public static DeploymentStatus GetFailedToFetch(string details) + => new("Failed to fetch", details, SeverityLevel.Error); + public static readonly DeploymentStatus Fetching = new("Fetching", string.Empty, SeverityLevel.Info); + public static readonly DeploymentStatus Fetched = new("Fetched", string.Empty, SeverityLevel.Success); + + public static DeploymentStatus GetFailedToDeploy(string details) + => new("Failed to deploy", details, SeverityLevel.Error); + public static readonly DeploymentStatus Deploying = new("Deploying", string.Empty, SeverityLevel.Info); + public static readonly DeploymentStatus Deployed = new("Deployed", string.Empty, SeverityLevel.Success); + } +} diff --git a/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Service/ICloudContentDeliveryClient.cs b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Service/ICloudContentDeliveryClient.cs new file mode 100644 index 0000000..458abf8 --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Service/ICloudContentDeliveryClient.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Unity.Services.CloudContentDelivery.Authoring.Core.Model; + +namespace Unity.Services.CloudContentDelivery.Authoring.Core.Service +{ + //This is a sample IServiceClient and might not map to your existing admin APIs + public interface ICloudContentDeliveryClient + { + void Initialize(string environmentId, string projectId, CancellationToken cancellationToken); + + Task Get(string name); + Task Update(IBucket bucket); + Task Create(IBucket bucket); + Task Delete(IBucket bucket); + Task> List(); + } +} diff --git a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Unity.Services.Access.Authoring.Core.csproj b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Unity.Services.CloudContentDelivery.Authoring.Core.csproj similarity index 85% rename from Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Unity.Services.Access.Authoring.Core.csproj rename to Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Unity.Services.CloudContentDelivery.Authoring.Core.csproj index f3f0370..28719a9 100644 --- a/Unity.Services.Cli/Unity.Services.Access.Authoring.Core/Unity.Services.Access.Authoring.Core.csproj +++ b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Unity.Services.CloudContentDelivery.Authoring.Core.csproj @@ -1,6 +1,6 @@ - net5.0 + net8.0 disable 0.0.1 disable @@ -12,13 +12,13 @@ - <_Parameter1>Unity.Services.Cli.Access.UnitTest + <_Parameter1>$(AssemblyName).UnitTest <_Parameter1>DynamicProxyGenAssembly2 - + \ No newline at end of file diff --git a/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Validations/DuplicateResourceValidation.cs b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Validations/DuplicateResourceValidation.cs new file mode 100644 index 0000000..563899b --- /dev/null +++ b/Unity.Services.Cli/Unity.Services.CloudContentDelivery.Authoring.Core/Validations/DuplicateResourceValidation.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using Unity.Services.CloudContentDelivery.Authoring.Core.Model; + +namespace Unity.Services.CloudContentDelivery.Authoring.Core.Validations +{ + static class DuplicateResourceValidation + { + public static IReadOnlyList FilterDuplicateResources( + IReadOnlyList resources, + out IReadOnlyList> duplicateGroups) + { + duplicateGroups = resources + .GroupBy(r => r.Id) + .Where(g => g.Count() > 1) + .ToList(); + + var hashset = new HashSet(duplicateGroups.Select(g => g.Key)); + + return resources + .Where(r => !hashset.Contains(r.Id)) + .ToList(); + } + + public static (string, string) GetDuplicateResourceErrorMessages( + IBucket targetBucket, + IReadOnlyList group) + { + var duplicates = group + .Except(new[] { targetBucket }) + .ToList(); + + var duplicatesStr = string.Join(", ", duplicates.Select(d => $"'{d.Path}'")); + var shortMessage = $"'{targetBucket.Path}' was found duplicated in other files: {duplicatesStr}"; + var message = $"Multiple resources with the same identifier '{targetBucket.Id}' were found. " + + "Only a single resource for a given identifier may be deployed/fetched at the same time. " + + "Give all resources unique identifiers or deploy/fetch them separately to proceed.\n" + + shortMessage; + return (shortMessage, message); + } + } +} diff --git a/Unity.Services.Cli/Unity.Services.ModuleTemplate.Authoring.Core/Unity.Services.ModuleTemplate.Authoring.Core.csproj b/Unity.Services.Cli/Unity.Services.ModuleTemplate.Authoring.Core/Unity.Services.ModuleTemplate.Authoring.Core.csproj index 35b7f71..a4a70d2 100644 --- a/Unity.Services.Cli/Unity.Services.ModuleTemplate.Authoring.Core/Unity.Services.ModuleTemplate.Authoring.Core.csproj +++ b/Unity.Services.Cli/Unity.Services.ModuleTemplate.Authoring.Core/Unity.Services.ModuleTemplate.Authoring.Core.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 disable 0.0.1 disable @@ -21,6 +21,6 @@ - + \ No newline at end of file diff --git a/Unity.Services.Cli/Unity.Services.Scheduler.Authoring.Core/Fetch/SchedulerFetchHandler.cs b/Unity.Services.Cli/Unity.Services.Scheduler.Authoring.Core/Fetch/SchedulerFetchHandler.cs index bacd930..7e945f7 100644 --- a/Unity.Services.Cli/Unity.Services.Scheduler.Authoring.Core/Fetch/SchedulerFetchHandler.cs +++ b/Unity.Services.Cli/Unity.Services.Scheduler.Authoring.Core/Fetch/SchedulerFetchHandler.cs @@ -103,7 +103,7 @@ public async Task FetchAsync(string rootDirectory, { var task = m_FileSystem.WriteAllText( resource.Path, - m_ScheduleSerializer.Serialize(new List(){resource}), + m_ScheduleSerializer.Serialize(new List() { resource }), token); createTasks.Add((resource, task)); } diff --git a/Unity.Services.Cli/Unity.Services.Scheduler.Authoring.Core/Model/Statuses.cs b/Unity.Services.Cli/Unity.Services.Scheduler.Authoring.Core/Model/Statuses.cs index 6d4c7aa..e6a4877 100644 --- a/Unity.Services.Cli/Unity.Services.Scheduler.Authoring.Core/Model/Statuses.cs +++ b/Unity.Services.Cli/Unity.Services.Scheduler.Authoring.Core/Model/Statuses.cs @@ -6,33 +6,33 @@ namespace Unity.Services.Scheduler.Authoring.Core.Model { public static class Statuses { - public static readonly DeploymentStatus FailedToLoad = new ("Failed to load", string.Empty, SeverityLevel.Error); + public static readonly DeploymentStatus FailedToLoad = new("Failed to load", string.Empty, SeverityLevel.Error); public static DeploymentStatus GetFailedToFetch(string details) - => new ("Failed to fetch", details, SeverityLevel.Error); - public static readonly DeploymentStatus Fetching = new ("Fetching", string.Empty, SeverityLevel.Info); - public static DeploymentStatus GetFetched(string detail) => new ("Fetched", detail, SeverityLevel.Success); + => new("Failed to fetch", details, SeverityLevel.Error); + public static readonly DeploymentStatus Fetching = new("Fetching", string.Empty, SeverityLevel.Info); + public static DeploymentStatus GetFetched(string detail) => new("Fetched", detail, SeverityLevel.Success); public static DeploymentStatus GetFailedToDeploy(string details) - => new ("Failed to deploy", details, SeverityLevel.Error); + => new("Failed to deploy", details, SeverityLevel.Error); public static DeploymentStatus GetDeploying(string details = null) - => new ( "Deploying", details ?? string.Empty, SeverityLevel.Info); + => new("Deploying", details ?? string.Empty, SeverityLevel.Info); public static DeploymentStatus GetDeployed(string details) - => new ("Deployed", details, SeverityLevel.Success); + => new("Deployed", details, SeverityLevel.Success); public static DeploymentStatus GetFailedToLoad(Exception e, string path) - => new ("Failed to load", $"Failed to load '{path}'. Reason: {e.Message}", SeverityLevel.Error); + => new("Failed to load", $"Failed to load '{path}'. Reason: {e.Message}", SeverityLevel.Error); public static DeploymentStatus GetFailedToRead(Exception e, string path) - => new ("Failed to read", $"Failed to read '{path}'. Reason: {e.Message}", SeverityLevel.Error); + => new("Failed to read", $"Failed to read '{path}'. Reason: {e.Message}", SeverityLevel.Error); public static DeploymentStatus GetFailedToWrite(Exception e, string path) - => new ("Failed to write", $"Failed to write '{path}'. Reason: {e.Message}", SeverityLevel.Error); + => new("Failed to write", $"Failed to write '{path}'. Reason: {e.Message}", SeverityLevel.Error); public static DeploymentStatus GetFailedToSerialize(Exception e, string path) - => new ("Failed to serialize", $"Failed to serialize '{path}'. Reason: {e.Message}", SeverityLevel.Error); + => new("Failed to serialize", $"Failed to serialize '{path}'. Reason: {e.Message}", SeverityLevel.Error); public static DeploymentStatus GetFailedToDelete(Exception e, string path) - => new ("Failed to serialize", $"Failed to delete '{path}'. Reason: {e.Message}", SeverityLevel.Error); + => new("Failed to serialize", $"Failed to delete '{path}'. Reason: {e.Message}", SeverityLevel.Error); } } diff --git a/Unity.Services.Cli/Unity.Services.Scheduler.Authoring.Core/Unity.Services.Scheduler.Authoring.Core.csproj b/Unity.Services.Cli/Unity.Services.Scheduler.Authoring.Core/Unity.Services.Scheduler.Authoring.Core.csproj index 9087581..28719a9 100644 --- a/Unity.Services.Cli/Unity.Services.Scheduler.Authoring.Core/Unity.Services.Scheduler.Authoring.Core.csproj +++ b/Unity.Services.Cli/Unity.Services.Scheduler.Authoring.Core/Unity.Services.Scheduler.Authoring.Core.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 disable 0.0.1 disable @@ -19,6 +19,6 @@ - + \ No newline at end of file diff --git a/Unity.Services.Cli/Unity.Services.Triggers.Authoring.Core/Unity.Services.Triggers.Authoring.Core.csproj b/Unity.Services.Cli/Unity.Services.Triggers.Authoring.Core/Unity.Services.Triggers.Authoring.Core.csproj index 2ccdbfe..042c245 100644 --- a/Unity.Services.Cli/Unity.Services.Triggers.Authoring.Core/Unity.Services.Triggers.Authoring.Core.csproj +++ b/Unity.Services.Cli/Unity.Services.Triggers.Authoring.Core/Unity.Services.Triggers.Authoring.Core.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 disable 0.0.1 disable @@ -19,7 +19,7 @@ - + diff --git a/test.runsettings b/test.runsettings index 3ec2901..577532d 100644 --- a/test.runsettings +++ b/test.runsettings @@ -2,7 +2,7 @@ ./TestResults ./TestResults - 1800000 + 3600000