// Copyright Epic Games, Inc. All Rights Reserved. #include "Misc/AutomationTest.h" #if WITH_EDITOR #include "WebAPIEditorSettings.h" #include "Dom/WebAPIParameter.h" #include "Misc/AutomationTest.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "Serialization/JsonSerializer.h" #include "V3/WebAPIOpenAPIConverter.h" #include "V3/WebAPIOpenAPIFactory.h" #include "V3/WebAPIOpenAPISchema.h" #if WITH_DEV_AUTOMATION_TESTS BEGIN_DEFINE_SPEC(FWebAPIOpenAPI3Spec, "Plugin.WebAPI.OpenAPI3", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter | EAutomationTestFlags::ApplicationContextMask) TSharedPtr InputDefinition; TStrongObjectPtr OutputDefinition; //TSharedPtr Converter; TStrongObjectPtr Factory; template void LoadFromJson(const TSharedPtr& InSchemaObject, const TSharedPtr& InJsonObject) { InSchemaObject->FromJson(InJsonObject.ToSharedRef()); } template <> void LoadFromJson(const TSharedPtr& InSchemaObject, const TSharedPtr& InJsonObject) { UE::WebAPI::OpenAPI::V3::FPathsObject& SchemaObject = InSchemaObject.ToSharedRef().Get(); UE::Json::FromJson(InJsonObject.ToSharedRef(), SchemaObject); } template void LoadFromJson(const TArray>& InSchemaObject, const TSharedPtr& InJsonValue) { const TArray>* JsonArray = nullptr; if(InJsonValue->TryGetArray(JsonArray)) { for(const TSharedPtr& JsonItem : *JsonArray) { TSharedPtr SchemaObject = MakeShared(); SchemaObject->FromJson(JsonItem->AsObject().ToSharedRef()); } } } template typename TEnableIf::Value, void>::Type TryAssign(TSharedPtr& InDst, const TSharedPtr& InSrc) { } template typename TEnableIf::Value, void>::Type TryAssign(TSharedPtr& InDst, const TSharedPtr& InSrc) { InDst = InSrc; } template TSharedPtr InitializeForFile( const FString& InFile, const TUniqueFunction&, const TSharedPtr&)>& InAttachFunc = {}) { ensureAlways(FPaths::FileExists(InFile)); const TSharedPtr JsonObject = LoadJson(InFile); TSharedPtr Input = MakeShared(); LoadFromJson(Input, JsonObject); if (InAttachFunc) { InputDefinition = MakeShared(); InAttachFunc(InputDefinition, Input); } else { TryAssign(InputDefinition, Input); } OutputDefinition = TStrongObjectPtr(NewObject()); Factory = TStrongObjectPtr(NewObject()); return MakeShared( InputDefinition, OutputDefinition->GetWebAPISchema(), OutputDefinition->GetMessageLog().ToSharedRef(), OutputDefinition->GetProviderSettings()); } template TSharedPtr InitializeArrayForFile( const FString& InFile, const TUniqueFunction&, const TArray>&)>& InAttachFunc = {}) { ensureAlways(FPaths::FileExists(InFile)); TSharedPtr JsonValueArray = LoadJsonArray(InFile); TArray> InputArray; LoadFromJson(InputArray, JsonValueArray); if(InAttachFunc) { InputDefinition = MakeShared(); InAttachFunc(InputDefinition, InputArray); } OutputDefinition = TStrongObjectPtr(NewObject()); Factory = TStrongObjectPtr(NewObject()); return MakeShared( InputDefinition, OutputDefinition->GetWebAPISchema(), OutputDefinition->GetMessageLog().ToSharedRef(), OutputDefinition->GetProviderSettings()); } FString GetSampleFile(const FString& InName) const { FString FilePath = FPaths::Combine(FPaths::ProjectPluginsDir(), TEXT("WebAPI"), TEXT("Source"), TEXT("WebAPIOpenAPI"), TEXT("Private"), TEXT("Tests"), TEXT("Samples"), TEXT("V3"), InName + TEXT(".json")); ensure(FPaths::FileExists(FilePath)); return FilePath; } FString GetAPISample() const { return GetSampleFile(TEXT("petstore_V3")); } TSharedPtr LoadJson(const FString& InFile) const { FString FileContents; FFileHelper::LoadFileToString(FileContents, *InFile); TSharedPtr JsonObject; FJsonSerializer::Deserialize(TJsonReaderFactory::Create(FileContents), JsonObject); return JsonObject; } TSharedPtr LoadJsonArray(const FString& InFile) const { FString FileContents; FFileHelper::LoadFileToString(FileContents, *InFile); TArray> JsonValueArray; FJsonSerializer::Deserialize(TJsonReaderFactory::Create(FileContents), JsonValueArray); return MakeShared(JsonValueArray); } template TObjectPtr FindNamedModel(const TArray>& InModels, const FString& InName) const { static_assert(TIsDerivedFrom::Value, "InputModelType is not derived from UWebAPIModelBase."); static_assert(TIsDerivedFrom::Value, "OutputModelType is not derived from InputModelType."); const TObjectPtr* FoundModel = InModels.FindByPredicate([InName](const TObjectPtr InModelBase) { // @note: order matters! if(const TObjectPtr Parameter = Cast(InModelBase)) { return Parameter->Name.ToString(true).Equals(InName); } else if(const TObjectPtr Model = Cast(InModelBase)) { return Model->Name.ToString(true).Equals(InName); } else if(const TObjectPtr Enum = Cast(InModelBase)) { return Enum->Name.ToString(true).Equals(InName); } return false; }); return FoundModel ? Cast(*FoundModel) : nullptr; } template TObjectPtr FindNamedModel(const TArray>& InModels, const FString& InName) const { static_assert(TIsDerivedFrom::Value, "OutputModelType is not derived from UWebAPIModelBase."); const TObjectPtr* FoundModel = InModels.FindByPredicate([InName](const TObjectPtr InModelBase) { // @note: order matters! if(const TObjectPtr Parameter = Cast(InModelBase)) { return Parameter->Name.ToString(true).Equals(InName); } else if(const TObjectPtr Model = Cast(InModelBase)) { return Model->Name.ToString(true).Equals(InName); } else if(const TObjectPtr Enum = Cast(InModelBase)) { return Enum->Name.ToString(true).Equals(InName); } return false; }); return FoundModel ? Cast(*FoundModel) : nullptr; } TObjectPtr FindNamedProperty(const TObjectPtr& InModel, const FString& InName, const FString& InTypeName = {}) const { const TObjectPtr* FoundProperty = InModel->Properties.FindByPredicate([InName, InTypeName](const TObjectPtr InProperty) { if(InTypeName.IsEmpty()) { return InProperty->Name.ToString(true).Equals(InName); } else { return InProperty->Name.ToString(true).Equals(InName) && InProperty->Type.ToString(true).Equals(InTypeName); } }); return FoundProperty ? *FoundProperty : nullptr; } //~ Begin FAutomationTestBase extensions template bool TestNull(const FString& What, TObjectPtr Pointer) { return FAutomationTestBase::TestNull(*What, Pointer.Get()); } template bool TestNotNull(const FString& What, TObjectPtr Pointer) { return FAutomationTestBase::TestNotNull(*What, Pointer.Get()); } bool TestFoundProperty(const FString& What, const TObjectPtr& InModel, const FString& InName, const FString& InTypeName = {}) { const TObjectPtr* FoundProperty = InModel->Properties.FindByPredicate([InName, InTypeName](const TObjectPtr InProperty) { if(InTypeName.IsEmpty()) { return InProperty->Name.ToString(true).Equals(InName); } else { return InProperty->Name.ToString(true).Equals(InName) && InProperty->Type.ToString(true).Equals(InTypeName); } }); if (FoundProperty == nullptr) { if(InTypeName.IsEmpty()) { AddError(FString::Printf(TEXT("Expected a property named '%s'."), *InName), 1); } else { AddError(FString::Printf(TEXT("Expected a property named '%s' of type '%s'."), *InName, *InTypeName), 1); } return false; } return TestTrue(What, true); } //~ End FAutomationTestBase Interface END_DEFINE_SPEC(FWebAPIOpenAPI3Spec) // @todo: remove when done: -ExecCmds="Automation RunTests Plugin.WebAPI.OpenAPI3" -testexit="Automation Test Queue Empty" -unattended -nopause void FWebAPIOpenAPI3Spec::Define() { BeforeEach([this] { Factory.Reset(); InputDefinition.Reset(); OutputDefinition.Reset(); }); Describe("Petstore", [this] { It("Parse", [this] { const FString FilePath = GetAPISample(); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FOpenAPIObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("ComponentsObject", [this] { It("Converts", [this] { const FString FilePath = GetSampleFile("ComponentsObjectExample"); const TSharedPtr Converter = InitializeForFile(FilePath); const bool bWasConverted = Converter->Convert(); if(TestTrue("ComponentsObject converted to WebAPI", bWasConverted)) { const TArray> AllConvertedModels = OutputDefinition->GetWebAPISchema()->Models; // Test Models { TArray> ConvertedModels; for(const TObjectPtr& Model : AllConvertedModels) { if(Model->IsA(UWebAPIModel::StaticClass())) { ConvertedModels.Add(Cast(Model)); } } // Can be populated with other models, ie. nested models so check that it contains AT LEAST 3 TestTrue("Converted 3 models", ConvertedModels.Num() >= 3); const TObjectPtr GeneralErrorModel = FindNamedModel(ConvertedModels, TEXT("GeneralError")); if(TestNotNull("GeneralError model found", GeneralErrorModel)) { TestFoundProperty("Code property found", GeneralErrorModel, TEXT("Code"), TEXT("int32")); TestFoundProperty("Message property found", GeneralErrorModel, TEXT("Message"), TEXT("String")); } const TObjectPtr CategoryModel = FindNamedModel(ConvertedModels, TEXT("Category")); if(TestNotNull("Category model found", CategoryModel)) { TestFoundProperty("Id property found", CategoryModel, TEXT("Id"), TEXT("int64")); TestFoundProperty("Name property found", CategoryModel, TEXT("Name"), TEXT("String")); } const TObjectPtr TagModel = FindNamedModel(ConvertedModels, TEXT("Tag")); if(TestNotNull("Tag model found", TagModel)) { TestFoundProperty("Id property found", TagModel, TEXT("Id"), TEXT("int64")); TestFoundProperty("Name property found", TagModel, TEXT("Name"), TEXT("String")); } } // Test Parameters { TArray> ConvertedParameters; for(const TObjectPtr& Model : AllConvertedModels) { if(Model->IsA(UWebAPIParameter::StaticClass())) { ConvertedParameters.Add(Cast(Model)); } } TestEqual("Converted 2 parameters", ConvertedParameters.Num(), 2); const TObjectPtr SkipParamParameter = FindNamedModel(ConvertedParameters, TEXT("SkipParam")); if(TestNotNull("SkipParam parameter found", SkipParamParameter)) { TestEqual("Parameter name is skip", SkipParamParameter->Name.ToString(true), TEXT("skip")); TestEqual("Parameter stored in query", SkipParamParameter->Storage, EWebAPIParameterStorage::Query); TestTrue("Parameter is required", SkipParamParameter->bIsRequired); TestEqual("Parameter is 32 bit integer", SkipParamParameter->Property->Type.ToString(true), TEXT("int32")); } const TObjectPtr LimitParamParameter = FindNamedModel(ConvertedParameters, TEXT("LimitParam")); if(TestNotNull("LimitParam parameter found", LimitParamParameter)) { TestEqual("Parameter name is limit", LimitParamParameter->Name.ToString(true), TEXT("limit")); TestEqual("Parameter stored in query", LimitParamParameter->Storage, EWebAPIParameterStorage::Query); TestTrue("Parameter is required", LimitParamParameter->bIsRequired); TestEqual("Parameter is 32 bit integer", LimitParamParameter->Property->Type.ToString(true), TEXT("int32")); } } } }); }); Describe("PathsObject", [this] { It("Converts", [this] { const FString FilePath = GetSampleFile("PathsObjectExample"); const TSharedPtr Converter = InitializeForFile( FilePath, [](const TSharedPtr InRootObject, const TSharedPtr& InPathsObject) { InRootObject->Paths = *InPathsObject; }); const bool bWasConverted = Converter->Convert(); if(TestTrue("PathsObject converted to WebAPI", bWasConverted)) { const TMap> ConvertedServices = OutputDefinition->GetWebAPISchema()->Services; TestTrue("Converted 1 service", ConvertedServices.Num() >= 1); TArray> ServiceArray; ConvertedServices.GenerateValueArray(ServiceArray); const TObjectPtr FirstService = ServiceArray[0]; TestTrue("Service has 1 operation", FirstService->Operations.Num() >= 1); const TObjectPtr GetPetsOperation = FirstService->Operations[0]; if(TestNotNull("Get Pets operation found", GetPetsOperation)) { TestTrue("Operation has 1 response", GetPetsOperation->Responses.Num() >= 1); const TObjectPtr FirstResponse = GetPetsOperation->Responses[0]; if(TestEqual("Response is for code 200", FirstResponse->Code, 200)) { TestTrue("Response has 1 property", FirstResponse->Properties.Num() >= 1); const TObjectPtr FirstProperty = FirstResponse->Properties[0]; if(TestEqual("Response contains a property named Pets", FirstProperty->Name.ToString(true), TEXT("Pets"))) { TestTrue("Pets property is an array", FirstProperty->bIsArray); TestEqual("Pets property is stored in the message body", FirstResponse->Storage, EWebAPIResponseStorage::Body); } } } } }); }); Describe("ServersObject", [this] { // @todo: support server variables? It("Converts with Variables", [this] { const FString FilePath = GetSampleFile("ServerObjectExample_Variables"); const TSharedPtr Converter = InitializeArrayForFile( FilePath, [](const TSharedPtr InRootObject, const TArray>& InServersObject) { InRootObject->Servers = InServersObject; }); const bool bWasConverted = Converter->Convert(); if(TestTrue("ServersObject converted to WebAPI", bWasConverted)) { const FString ExpectedUrl = TEXT("https://{username}.gigantic-server.com:{port}/{basePath}"); TestEqual(FString::Printf(TEXT("Host is \"%s\""), *ExpectedUrl), OutputDefinition->GetWebAPISchema()->Host, ExpectedUrl); } }); }); Describe("InfoObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("InfoObjectExample"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FInfoObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("OpenAPIObject", [this] { It("Parse", [this] { }); }); Describe("ContactObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("ContactObjectExample"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FContactObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("LicenseObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("LicenseObjectExample"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FLicenseObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("ServerVariableObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("ServerObjectExample_Variables"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FServerObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("PathItemObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("PathItemObjectExample"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FPathItemObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("ResponseObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("ResponseObjectExample"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FResponseObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("OperationObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("OperationObjectExample"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FOperationObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("ExternalDocumentationObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("ExternalDocumentationObjectExample"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FExternalDocumentationObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("ParameterObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("ParameterObjectExample"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FParameterObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("RequestBodyObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("RequestBodyObjectExample"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FRequestBodyObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("MediaTypeObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("MediaTypeObjectExample"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FMediaTypeObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("EncodingObject", [this] { It("Parse", [this] { // const FString FilePath = GetSampleFile("EncodingObjectExample"); // const TSharedPtr JsonObject = LoadJson(FilePath); // // UE::WebAPI::OpenAPI::V3::FEncodingObject Object; // Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("CallbackObject", [this] { It("Parse", [this] { // const FString FilePath = GetSampleFile("CallbackObjectExample"); // const TSharedPtr JsonObject = LoadJson(FilePath); // // UE::WebAPI::OpenAPI::V3::FCallbackObject Object; // Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("ExampleObject", [this] { It("Parse", [this] { // const FString FilePath = GetSampleFile("ExampleObjectExample"); // const TSharedPtr JsonObject = LoadJson(FilePath); // // UE::WebAPI::OpenAPI::V3::FExampleObject Object; // Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("LinkObject", [this] { It("Parse", [this] { // const FString FilePath = GetSampleFile("LinkObjectExample"); // const TSharedPtr JsonObject = LoadJson(FilePath); // // UE::WebAPI::OpenAPI::V3::FLinkObject Object; // Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("HeaderObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("HeaderObjectExample"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FHeaderObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("TagObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("TagObjectExample"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FTagObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("ReferenceObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("ReferenceObjectExample"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FReferenceObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("SchemaObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("SchemaObjectExample"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FSchemaObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("DiscriminatorObject", [this] { It("Parse", [this] { // const FString FilePath = GetSampleFile("DiscriminatorObjectExample"); // const TSharedPtr JsonObject = LoadJson(FilePath); // // UE::WebAPI::OpenAPI::V3::FDiscriminatorObject Object; // Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("XMLObject", [this] { It("Parse", [this] { }); }); Describe("SecuritySchemeObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("SecuritySchemeObject_ApiKey"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FSecuritySchemeObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("OAuthFlowsObject", [this] { It("Parse", [this] { }); }); Describe("OAuthFlowObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("OAuthFlowObjectExample"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FOAuthFlowObject Object; Object.FromJson(JsonObject.ToSharedRef()); }); }); Describe("SecurityRequirementObject", [this] { It("Parse", [this] { const FString FilePath = GetSampleFile("SecurityRequirementObjectExample_OAuth2"); const TSharedPtr JsonObject = LoadJson(FilePath); UE::WebAPI::OpenAPI::V3::FSecurityRequirementObject Object; UE::Json::FromJson(JsonObject.ToSharedRef(), Object); //Object.FromJson(JsonObject.ToSharedRef()); }); }); } #endif #endif