From 733e79ab20ae06b8e6572d90fb0752750830ba46 Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Wed, 24 Feb 2021 16:15:30 -0800 Subject: [PATCH] (modelsRepo): Initial commit: Azure.Iot.ModelsRepository (#18998) * Initial Commit - Bring ModelRepository Project to the mono repo (#18706) * Use resx file and update formattings. * Add client diagnostics - Add sync APIs to RemoteFetcher and LocalFetcher (#18807) * Add async methods to the ResolverClient (#18814) * Add support for recorded tests. (#18836) * Add support for recorded tests. * Make strings constants * Update file names to include plural models * Refactor RepositoryHandler and ResolverClient (#18857) * Refactor ModelQuery (#18843) * Refactor ModelQuery. * Combine constant files. * Rename Client to ModelsRepoClient (#18862) * Remove ResolverException in favor of RequestFailedException (#18874) * Remove ResolverException in favor of RequestFailedException. Adds more docstrings. * Rename Repo to Repository and minor changes / Import samples project (#18986) * Format changes/Remove resx file. * Add first draft of readme. Add comment for dtmi validation. (#18987) * Add comment for dtmi validation. * Fix links. * Add diagnostic event info. * Source link specifically pointing to 'src' folder. * Use fluent assertion * Use playback as default * fix Links and root service directory casing Co-authored-by: Paymaun --- .github/CODEOWNERS | 4 + .../Azure.Iot.ModelsRepository/.gitignore | 1 + .../Azure.Iot.ModelsRepository.sln | 43 +++ .../Azure.Iot.ModelsRepository/CHANGELOG.md | 19 + .../CodeMaid.config | 41 +++ .../Directory.Build.props | 6 + .../Azure.Iot.ModelsRepository/README.md | 94 +++++ .../ModelsRepositoryClientSamples.csproj | 15 + .../ModelsRepositoryClientSamples/Program.cs | 53 +++ .../samples/readme.md | 0 .../src/Azure.Iot.ModelsRepository.csproj | 57 +++ .../src/DependencyResolutionOption.cs | 26 ++ .../src/DtmiConventions.cs | 55 +++ .../src/Fetchers/FetchResult.cs | 21 ++ .../src/Fetchers/IModelFetcher.cs | 20 ++ .../src/Fetchers/LocalModelFetcher.cs | 89 +++++ .../src/Fetchers/RemoteModelFetcher.cs | 254 +++++++++++++ .../src/ModelMetadata.cs | 26 ++ .../src/ModelQuery.cs | 224 ++++++++++++ .../src/ModelRepositoryConstants.cs | 33 ++ .../src/ModelsRepositoryClient.cs | 195 ++++++++++ .../src/ModelsRepositoryClientOptions.cs | 56 +++ .../src/ModelsRepositoryEventSource.cs | 111 ++++++ .../src/Properties/AssemblyInfo.cs | 7 + .../src/RepositoryHandler.cs | 162 +++++++++ .../src/StandardStrings.cs | 19 + .../Azure.Iot.ModelsRepository.Tests.csproj | 35 ++ .../tests/ClientTests.cs | 59 ++++ .../tests/DtmiConversionTests.cs | 51 +++ .../tests/ModelQueryTests.cs | 189 ++++++++++ .../tests/ModelsRepositoryRecordedTestBase.cs | 48 +++ .../tests/ModelsRepositoryTestBase.cs | 49 +++ .../tests/ModelsRepositoryTestEnvironment.cs | 11 + .../tests/ResolveIntegrationTests.cs | 288 +++++++++++++++ ...Exception(%com%example%Thermostat;1%).json | 6 + ...tion(%com%example%Thermostat;1%)Async.json | 6 + ...ion(%dtmi%com%example%%Thermostat;1%).json | 6 + ...dtmi%com%example%%Thermostat;1%)Async.json | 6 + ...tion(%dtmi%com%example%Thermostat%1%).json | 6 + ...%dtmi%com%example%Thermostat%1%)Async.json | 6 + .../ResolveMultipleModelsNoDeps(Local).json | 6 + ...solveMultipleModelsNoDeps(Local)Async.json | 6 + .../ResolveMultipleModelsNoDeps(Remote).json | 236 +++++++++++++ ...olveMultipleModelsNoDeps(Remote)Async.json | 236 +++++++++++++ ...xistentDtmiFileThrowsException(Local).json | 6 + ...ntDtmiFileThrowsException(Local)Async.json | 6 + ...istentDtmiFileThrowsException(Remote).json | 38 ++ ...tDtmiFileThrowsException(Remote)Async.json | 38 ++ .../ResolveSingleModelNoDeps(Local).json | 6 + .../ResolveSingleModelNoDeps(Local)Async.json | 6 + .../ResolveSingleModelNoDeps(Remote).json | 134 +++++++ ...ResolveSingleModelNoDeps(Remote)Async.json | 134 +++++++ ...olveSingleModelTryFromExpanded(Local).json | 6 + ...ingleModelTryFromExpanded(Local)Async.json | 6 + ...lveSingleModelTryFromExpanded(Remote).json | 260 ++++++++++++++ ...ngleModelTryFromExpanded(Remote)Async.json | 260 ++++++++++++++ .../ResolveSingleModelWithDeps(Local).json | 6 + ...esolveSingleModelWithDeps(Local)Async.json | 6 + .../ResolveSingleModelWithDeps(Remote).json | 334 ++++++++++++++++++ ...solveSingleModelWithDeps(Remote)Async.json | 334 ++++++++++++++++++ ...epsDisableDependencyResolution(Local).json | 6 + ...sableDependencyResolution(Local)Async.json | 6 + ...psDisableDependencyResolution(Remote).json | 134 +++++++ ...ableDependencyResolution(Remote)Async.json | 134 +++++++ ...WithWrongCasingThrowsException(Local).json | 6 + ...rongCasingThrowsException(Local)Async.json | 6 + ...ithWrongCasingThrowsException(Remote).json | 134 +++++++ ...ongCasingThrowsException(Remote)Async.json | 134 +++++++ .../devicemanagement/deviceinformation-1.json | 64 ++++ .../devicemanagement/deviceinformation-2.json | 16 + .../dtmi/com/example/base-1.json | 56 +++ .../dtmi/com/example/base-2.json | 57 +++ .../dtmi/com/example/building-1.json | 19 + .../dtmi/com/example/camera-3.json | 13 + .../dtmi/com/example/coldstorage-1.json | 13 + .../dtmi/com/example/conferenceroom-1.json | 13 + .../dtmi/com/example/freezer-1.json | 12 + .../incompleteexpanded-1.expanded.json | 151 ++++++++ .../dtmi/com/example/invalidmodel-1.json | 13 + .../dtmi/com/example/invalidmodel-2.json | 23 ++ .../dtmi/com/example/phone-2.json | 23 ++ .../dtmi/com/example/room-1.json | 12 + .../temperaturecontroller-1.expanded.json | 215 +++++++++++ .../com/example/temperaturecontroller-1.json | 60 ++++ .../dtmi/com/example/thermostat-1.json | 19 + .../dtmi/company/demodevice-1.json | 31 ++ .../dtmi/company/demodevice-2.json | 31 ++ .../dtmi/strict/badfilepath-1.json | 12 + .../dtmi/strict/emptyarray-1.json | 1 + .../dtmi/strict/namespaceconflict-1.json | 37 ++ .../TestModelRepo/dtmi/strict/nondtdl-1.json | 1 + .../dtmi/strict/unsupportedrootarray-1.json | 91 +++++ sdk/modelsrepository/ci.yml | 32 ++ sdk/modelsrepository/tests.yml | 8 + 94 files changed, 6044 insertions(+) create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/.gitignore create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/Azure.Iot.ModelsRepository.sln create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/CHANGELOG.md create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/CodeMaid.config create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/Directory.Build.props create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/README.md create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/samples/ModelsRepositoryClientSamples/ModelsRepositoryClientSamples.csproj create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/samples/ModelsRepositoryClientSamples/Program.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/samples/readme.md create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Azure.Iot.ModelsRepository.csproj create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DependencyResolutionOption.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DtmiConventions.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/FetchResult.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/IModelFetcher.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelMetadata.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelsRepositoryClient.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelsRepositoryClientOptions.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelsRepositoryEventSource.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Properties/AssemblyInfo.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/src/StandardStrings.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/Azure.Iot.ModelsRepository.Tests.csproj create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/DtmiConversionTests.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelQueryTests.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelsRepositoryRecordedTestBase.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelsRepositoryTestBase.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelsRepositoryTestEnvironment.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ResolveIntegrationTests.cs create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%com%example%Thermostat;1%).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%com%example%Thermostat;1%)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%%Thermostat;1%).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%%Thermostat;1%)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%Thermostat%1%).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%Thermostat%1%)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Local).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Local)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Remote).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Remote)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Local).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Local)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Remote).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Remote)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Local).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Local)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Remote).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Remote)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Local).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Local)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Remote).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Remote)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Local).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Local)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Remote).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Remote)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Local).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Local)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Remote).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Remote)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Local).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Local)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Remote).json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Remote)Async.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/azure/devicemanagement/deviceinformation-1.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/azure/devicemanagement/deviceinformation-2.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/base-1.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/base-2.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/building-1.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/camera-3.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/coldstorage-1.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/conferenceroom-1.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/freezer-1.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/incompleteexpanded-1.expanded.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/invalidmodel-1.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/invalidmodel-2.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/phone-2.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/room-1.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/temperaturecontroller-1.expanded.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/temperaturecontroller-1.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/thermostat-1.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/company/demodevice-1.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/company/demodevice-2.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/badfilepath-1.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/emptyarray-1.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/namespaceconflict-1.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/nondtdl-1.json create mode 100644 sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/unsupportedrootarray-1.json create mode 100644 sdk/modelsrepository/ci.yml create mode 100644 sdk/modelsrepository/tests.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1dd5f665f80e..090d43788d9c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -261,6 +261,10 @@ # ServiceLabel: %Digital Twins %Service Attention /sdk/digitaltwins/ @drwill-ms @timtay-microsoft @abhipsaMisra @vinagesh @azabbasi @bikamani @barustum @jamdavi +# PRLabel: %IoT Models Repository +# ServiceLabel: %IoT Models Repository %Service Attention +/sdk/modelsrepository/ @drwill-ms @timtay-microsoft @abhipsaMisra @vinagesh @azabbasi @bikamani @barustum @jamdavi @digimaun + # PRLabel: %TimeSeriesInsights # ServiceLabel: %TimeSeriesInsights %Service Attention /sdk/timeseriesinsights/ @drwill-ms @timtay-microsoft @abhipsaMisra @vinagesh @azabbasi @bikamani @barustum @jamdavi @yeskarthik @rasidhan @dmdenmsft diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/.gitignore b/sdk/modelsrepository/Azure.Iot.ModelsRepository/.gitignore new file mode 100644 index 000000000000..031950aa8414 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/.gitignore @@ -0,0 +1 @@ +launchSettings.json diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/Azure.Iot.ModelsRepository.sln b/sdk/modelsrepository/Azure.Iot.ModelsRepository/Azure.Iot.ModelsRepository.sln new file mode 100644 index 000000000000..cefb388cba6f --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/Azure.Iot.ModelsRepository.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30717.126 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Iot.ModelsRepository", "src\Azure.Iot.ModelsRepository.csproj", "{5E11A377-0D20-49F8-952B-50390196EF4B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Iot.ModelsRepository.Tests", "tests\Azure.Iot.ModelsRepository.Tests.csproj", "{092E6CE2-9998-428C-A801-2BAB4E14A577}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.TestFramework", "..\..\core\Azure.Core.TestFramework\src\Azure.Core.TestFramework.csproj", "{1FC8A3EA-3C0D-4DDF-B710-A7091F2CEBB1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ModelsRepositoryClientSamples", "samples\ModelsRepositoryClientSamples\ModelsRepositoryClientSamples.csproj", "{51E4AB9E-46F3-450C-B52A-C4C6378E8BA3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5E11A377-0D20-49F8-952B-50390196EF4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E11A377-0D20-49F8-952B-50390196EF4B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E11A377-0D20-49F8-952B-50390196EF4B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E11A377-0D20-49F8-952B-50390196EF4B}.Release|Any CPU.Build.0 = Release|Any CPU + {092E6CE2-9998-428C-A801-2BAB4E14A577}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {092E6CE2-9998-428C-A801-2BAB4E14A577}.Debug|Any CPU.Build.0 = Debug|Any CPU + {092E6CE2-9998-428C-A801-2BAB4E14A577}.Release|Any CPU.ActiveCfg = Release|Any CPU + {092E6CE2-9998-428C-A801-2BAB4E14A577}.Release|Any CPU.Build.0 = Release|Any CPU + {1FC8A3EA-3C0D-4DDF-B710-A7091F2CEBB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FC8A3EA-3C0D-4DDF-B710-A7091F2CEBB1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FC8A3EA-3C0D-4DDF-B710-A7091F2CEBB1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FC8A3EA-3C0D-4DDF-B710-A7091F2CEBB1}.Release|Any CPU.Build.0 = Release|Any CPU + {51E4AB9E-46F3-450C-B52A-C4C6378E8BA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51E4AB9E-46F3-450C-B52A-C4C6378E8BA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51E4AB9E-46F3-450C-B52A-C4C6378E8BA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51E4AB9E-46F3-450C-B52A-C4C6378E8BA3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {671D1EFB-2BB9-4846-91EF-A3FB1FF9DDA6} + EndGlobalSection +EndGlobal diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/CHANGELOG.md b/sdk/modelsrepository/Azure.Iot.ModelsRepository/CHANGELOG.md new file mode 100644 index 000000000000..fcda1deb5974 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/CHANGELOG.md @@ -0,0 +1,19 @@ +# Release History + +## 1.0.0-preview.1 (Unreleased) + +### New features + +- Initial preview of Azure.Iot.ModelsRepository SDK + +### Breaking changes + +- N/A + +### Added + +- N/A + +### Fixes and improvements + +- N/A diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/CodeMaid.config b/sdk/modelsrepository/Azure.Iot.ModelsRepository/CodeMaid.config new file mode 100644 index 000000000000..5382ce6fd8f0 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/CodeMaid.config @@ -0,0 +1,41 @@ + + + + +
+ + + + + + True + + + True + + + True + + + 120 + + + False + + + // Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + + + True + + + 1 + + + False + + + + diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/Directory.Build.props b/sdk/modelsrepository/Azure.Iot.ModelsRepository/Directory.Build.props new file mode 100644 index 000000000000..1a9611bd4924 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/Directory.Build.props @@ -0,0 +1,6 @@ + + + + diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/README.md b/sdk/modelsrepository/Azure.Iot.ModelsRepository/README.md new file mode 100644 index 000000000000..80d2603f1dec --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/README.md @@ -0,0 +1,94 @@ +# Azure IoT Models Repository client library for .NET + +This library provides functionality for interacting with the [Azure IoT Models Repository][modelsrepository_iot_endpoint]. It also aims to provide a consistent experience working with digital twin model repositories following Azure IoT conventions. + +[Source code][source] | Package (nuget) + +## Getting started + +The complete Microsoft Azure SDK can be downloaded from the [Microsoft Azure downloads][microsoft_sdk_download] page, and it ships with support for building deployment packages, integrating with tooling, rich command-line tooling, and more. + +For the best development experience, developers should use the official Microsoft NuGet packages for libraries. NuGet packages are regularly updated with new functionality and bug fixes. + +### Install the package + +Install the Azure IoT Models Repository client library for .NET with [NuGet][nuget]: + +```PowerShell +Install-Package Azure.Iot.ModelsRepository +``` + +View the package details at nuget.org. + +### Prerequisites + +- A models repository following [Azure IoT conventions][modelsrepository_conventions] + - The models repository can be located on the local filesystem or hosted on a webserver. + - Azure IoT hosts the global [Azure IoT Models Repository][modelsrepository_iot_endpoint] which the client will point to by default if no URI is provided. + +### Authenticate the client + +Currently no authentication mechanisms are supported in the client. The global endpoint is not tied to an Azure subscription and does not support auth. All models published are meant for anonymous public consumption. + +## Key concepts + +The Azure IoT Models Repository enables builders to manage and share digital twin models. The models are [JSON-LD][json_ld_reference] documents defined using the Digital Twins Definition Language ([DTDL][dtdlv2_reference]). + +The repository defines a pattern to store DTDL interfaces in a directory structure based on the Digital Twin Model Identifier (DTMI). You can locate an interface in the repository by converting the DTMI to a relative path. For example, the DTMI "`dtmi:com:example:Thermostat;1`" translates to `/dtmi/com/example/thermostat-1.json`. + +### Thread safety + +We guarantee that all client instance methods are thread-safe and independent of each other. See thread safety [guideline][thread_safety_guideline]. This ensures that the recommendation of reusing client instances is always safe, even across threads. + +### Additional concepts + + +[Client options](https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/core/Azure.Core/README.md#configuring-service-clients-using-clientoptions) | +[Accessing the response](https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/core/Azure.Core/README.md#accessing-http-response-details-using-responset) | +[Long-running operations](https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/core/Azure.Core/README.md#consuming-long-running-operations-using-operationt) | +[Handling failures](https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/core/Azure.Core/README.md#reporting-errors-requestfailedexception) | +[Diagnostics](https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/core/Azure.Core/samples/Diagnostics.md) | +[Mocking](https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/core/Azure.Core/README.md#mocking) | +[Client lifetime](https://devblogs.microsoft.com/azure-sdk/lifetime-management-and-thread-safety-guarantees-of-azure-sdk-net-clients/) + + +## Examples + +You can familiarize yourself with the client using [samples for IoT Models Repository][modelsrepository_samples]. + +## Troubleshooting + +All service operations will throw RequestFailedException on failure, with helpful error codes and other information. The client also produces diagnostic events and logging which can be listened to with an [EventListener][eventsourcelistener_reference]. + +## Next steps + +See implementation examples with our [code samples][modelsrepository_samples]. + +## Contributing + +This project welcomes contributions and suggestions. +Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. +For details, visit + +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). +Simply follow the instructions provided by the bot. +You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct][code_of_conduct]. +For more information see the [Code of Conduct FAQ][code_of_conduct_faq] or contact opencode@microsoft.com with any additional questions or comments. + + +[microsoft_sdk_download]: https://azure.microsoft.com/downloads/?sdk=net +[azure_sdk_target_frameworks]: https://github.com/azure/azure-sdk-for-net#target-frameworks +[source]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/modelsrepository/Azure.Iot.ModelsRepository/src +[code_of_conduct]: https://opensource.microsoft.com/codeofconduct/ +[code_of_conduct_faq]: https://opensource.microsoft.com/codeofconduct/faq/ +[nuget]: https://www.nuget.org/ +[azure_core_library]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/core/Azure.Core +[modelsrepository_conventions]: https://github.com/Azure/iot-plugandplay-models-tools/wiki +[modelsrepository_iot_endpoint]: https://devicemodels.azure.com/ +[modelsrepository_samples]: https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/modelsrepository/Azure.Iot.ModelsRepository/samples +[thread_safety_guideline]: https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-service-methods-thread-safety +[json_ld_reference]: https://json-ld.org +[dtdlv2_reference]: https://github.com/Azure/opendigitaltwins-dtdl/blob/master/DTDL/v2/dtdlv2.md +[eventsourcelistener_reference]: https://docs.microsoft.com/dotnet/api/azure.core.diagnostics.azureeventsourcelistener?view=azure-dotnet diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/samples/ModelsRepositoryClientSamples/ModelsRepositoryClientSamples.csproj b/sdk/modelsrepository/Azure.Iot.ModelsRepository/samples/ModelsRepositoryClientSamples/ModelsRepositoryClientSamples.csproj new file mode 100644 index 000000000000..216ea0215965 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/samples/ModelsRepositoryClientSamples/ModelsRepositoryClientSamples.csproj @@ -0,0 +1,15 @@ + + + Exe + net5.0 + Azure.Iot.ModelsRepository.Samples + + + + + + + + + + diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/samples/ModelsRepositoryClientSamples/Program.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/samples/ModelsRepositoryClientSamples/Program.cs new file mode 100644 index 000000000000..7255bdd7f115 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/samples/ModelsRepositoryClientSamples/Program.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core.Diagnostics; +using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using System.Net; +using System.Threading.Tasks; + +namespace Azure.Iot.ModelsRepository.Samples +{ + public class Program + { + public static async Task Main(string[] args) + { + // Forward all the events written to the console output with a specific format. + using AzureEventSourceListener listener = new AzureEventSourceListener( + (e, message) => + Console.WriteLine("[{0:HH:mm:ss:fff}][{1}] {2}", DateTimeOffset.Now, e.Level, message), + level: EventLevel.Verbose); + + await ResolveExistingAsync(); + await TryResolveButNotFoundAsync(); + } + + private static async Task ResolveExistingAsync() + { + var dtmi = "dtmi:com:example:TemperatureController;1"; + var client = new ModelsRepositoryClient(); + + IDictionary models = await client.ResolveAsync(dtmi).ConfigureAwait(false); + + Console.WriteLine($"{dtmi} resolved in {models.Count} interfaces."); + } + + private static async Task TryResolveButNotFoundAsync() + { + var dtmi = "dtmi:com:example:NotFound;1"; + var client = new ModelsRepositoryClient(); + + try + { + IDictionary models = await client.ResolveAsync(dtmi).ConfigureAwait(false); + Console.WriteLine($"{dtmi} resolved in {models.Count} interfaces."); + } + catch (RequestFailedException ex) when (ex.Status == (int)HttpStatusCode.NotFound) + { + Console.WriteLine($"{dtmi} was not found in the default public models repository: {ex.Message}"); + } + } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/samples/readme.md b/sdk/modelsrepository/Azure.Iot.ModelsRepository/samples/readme.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Azure.Iot.ModelsRepository.csproj b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Azure.Iot.ModelsRepository.csproj new file mode 100644 index 000000000000..025d552cbd33 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Azure.Iot.ModelsRepository.csproj @@ -0,0 +1,57 @@ + + + + Azure IoT Models Repository SDK + $(RequiredTargetFrameworks) + + true + + + + + IoT;ModelsRepository;Pnp;DigitalTwins$(PackageCommonTags) + SDK for the Azure IoT Models Repository + 1.0.0-preview.1 + + + + + + + + + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + Shared\Azure.Core + + + + + diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DependencyResolutionOption.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DependencyResolutionOption.cs new file mode 100644 index 000000000000..5d570a5378de --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DependencyResolutionOption.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.Iot.ModelsRepository +{ + /// + /// The dependency processing options. + /// + public enum DependencyResolutionOption + { + /// + /// Do not process external dependencies. + /// + Disabled, + + /// + /// Enable external dependencies. + /// + Enabled, + + /// + /// Try to get external dependencies using .expanded.json. + /// + TryFromExpanded + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DtmiConventions.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DtmiConventions.cs new file mode 100644 index 000000000000..74b25faec5ac --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/DtmiConventions.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace Azure.Iot.ModelsRepository +{ + /// + /// DtmiConventions implements the core aspects of the IoT model repo conventions + /// which includes DTMI validation and calculating a URI path from a DTMI. + /// + internal static class DtmiConventions + { + // A dtmi has three components: scheme, path, and version. + // Scheme and path are separated by a colon. Path and version are separated by a semicolon i.e. : ; . + // The scheme is the string literal "dtmi" in lowercase. The path is a sequence of one or more segments, separated by colons. + // The version is a sequence of one or more digits. Each path segment is a non-empty string containing only letters, digits, and underscores. + // The first character may not be a digit, and the last character may not be an underscore. + // The version length is limited to nine digits, because the number 999,999,999 fits in a 32-bit signed integer value. + // The first digit may not be zero, so there is no ambiguity regarding whether version 1 matches version 01 since the latter is invalid. + private static readonly Regex s_validDtmiRegex = new Regex(@"^dtmi:[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?(?::[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?)*;[1-9][0-9]{0,8}$"); + + public static bool IsDtmi(string dtmi) => !string.IsNullOrEmpty(dtmi) && s_validDtmiRegex.IsMatch(dtmi); + + public static string DtmiToPath(string dtmi) => IsDtmi(dtmi) ? $"{dtmi.ToLowerInvariant().Replace(":", "/").Replace(";", "-")}.json" : null; + + public static string DtmiToQualifiedPath(string dtmi, string basePath, bool fromExpanded = false) + { + string dtmiPath = DtmiToPath(dtmi); + + if (dtmiPath == null) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, StandardStrings.InvalidDtmiFormat, dtmi)); + } + + if (!basePath.EndsWith("/", StringComparison.InvariantCultureIgnoreCase)) + { + basePath += "/"; + } + + string fullyQualifiedPath = $"{basePath}{dtmiPath}"; + + if (fromExpanded) + { + fullyQualifiedPath = fullyQualifiedPath.Replace( + ModelRepositoryConstants.JsonFileExtension, + ModelRepositoryConstants.ExpandedJsonFileExtension); + } + + return fullyQualifiedPath; + } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/FetchResult.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/FetchResult.cs new file mode 100644 index 000000000000..7daa21b96453 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/FetchResult.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.Iot.ModelsRepository.Fetchers +{ + /// + /// The FetchResult class has the purpose of containing key elements of + /// an IModelFetcher Fetch() operation including model definition, path and whether + /// it was from an expanded (pre-calculated) fetch. + /// + internal class FetchResult + { + public string Definition { get; set; } + + public string Path { get; set; } + + public bool FromExpanded => Path.EndsWith( + ModelRepositoryConstants.ExpandedJsonFileExtension, + System.StringComparison.InvariantCultureIgnoreCase); + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/IModelFetcher.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/IModelFetcher.cs new file mode 100644 index 000000000000..0c86d875d33f --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/IModelFetcher.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Azure.Iot.ModelsRepository.Fetchers +{ + /// + /// The IModelFetcher is an abstraction that supports fetching + /// model content via a particular protocol or mechanism of interaction. + /// + internal interface IModelFetcher + { + Task FetchAsync(string dtmi, Uri repositoryUri, CancellationToken cancellationToken = default); + + FetchResult Fetch(string dtmi, Uri repositoryUri, CancellationToken cancellationToken = default); + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs new file mode 100644 index 000000000000..c317940a937d --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/LocalModelFetcher.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Threading.Tasks; +using System.Text; +using System.Collections.Generic; +using System.Threading; +using System.Globalization; +using Azure.Core.Pipeline; + +namespace Azure.Iot.ModelsRepository.Fetchers +{ + /// + /// The LocalModelFetcher is an implementation of IModelFetcher + /// for supporting local filesystem based model content fetching. + /// + internal class LocalModelFetcher : IModelFetcher + { + private readonly bool _tryExpanded; + private readonly ClientDiagnostics _clientDiagnostics; + + public LocalModelFetcher(ClientDiagnostics clientDiagnostics, ModelsRepositoryClientOptions clientOptions) + { + _clientDiagnostics = clientDiagnostics; + _tryExpanded = clientOptions.DependencyResolution == DependencyResolutionOption.TryFromExpanded; + } + + public Task FetchAsync(string dtmi, Uri repositoryUri, CancellationToken cancellationToken = default) + { + return Task.FromResult(Fetch(dtmi, repositoryUri, cancellationToken)); + } + + public FetchResult Fetch(string dtmi, Uri repositoryUri, CancellationToken cancellationToken = default) + { + using DiagnosticScope scope = _clientDiagnostics.CreateScope("LocalModelFetcher.Fetch"); + scope.Start(); + + try + { + var work = new Queue(); + + if (_tryExpanded) + { + work.Enqueue(GetPath(dtmi, repositoryUri, true)); + } + + work.Enqueue(GetPath(dtmi, repositoryUri, false)); + + string fnfError = string.Empty; + while (work.Count != 0) + { + cancellationToken.ThrowIfCancellationRequested(); + + string tryContentPath = work.Dequeue(); + ModelsRepositoryEventSource.Instance.FetchingModelContent(tryContentPath); + + if (File.Exists(tryContentPath)) + { + return new FetchResult + { + Definition = File.ReadAllText(tryContentPath, Encoding.UTF8), + Path = tryContentPath + }; + } + + ModelsRepositoryEventSource.Instance.ErrorFetchingModelContent(tryContentPath); + fnfError = string.Format(CultureInfo.InvariantCulture, StandardStrings.ErrorFetchingModelContent, tryContentPath); + } + + throw new RequestFailedException( + $"{string.Format(CultureInfo.InvariantCulture, StandardStrings.GenericResolverError, dtmi)} {fnfError}", + new FileNotFoundException(fnfError)); + } + catch (Exception ex) + { + scope.Failed(ex); + throw; + } + } + + private static string GetPath(string dtmi, Uri repositoryUri, bool expanded = false) + { + string registryPath = repositoryUri.AbsolutePath; + return DtmiConventions.DtmiToQualifiedPath(dtmi, registryPath, expanded); + } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs new file mode 100644 index 000000000000..e7649c33d1df --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Fetchers/RemoteModelFetcher.cs @@ -0,0 +1,254 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core; +using Azure.Core.Pipeline; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Azure.Iot.ModelsRepository.Fetchers +{ + /// + /// The RemoteModelFetcher is an implementation of IModelFetcher + /// for supporting http[s] based model content fetching. + /// + internal class RemoteModelFetcher : IModelFetcher + { + private readonly HttpPipeline _pipeline; + private readonly ClientDiagnostics _clientDiagnostics; + private readonly bool _tryExpanded; + + public RemoteModelFetcher(ClientDiagnostics clientDiagnostics, ModelsRepositoryClientOptions clientOptions) + { + _pipeline = CreatePipeline(clientOptions); + _tryExpanded = clientOptions.DependencyResolution == DependencyResolutionOption.TryFromExpanded; + _clientDiagnostics = clientDiagnostics; + } + + public FetchResult Fetch(string dtmi, Uri repositoryUri, CancellationToken cancellationToken = default) + { + using DiagnosticScope scope = _clientDiagnostics.CreateScope("RemoteModelFetcher.Fetch"); + scope.Start(); + try + { + Queue work = PrepareWork(dtmi, repositoryUri); + + string remoteFetchError = string.Empty; + + while (work.Count != 0) + { + cancellationToken.ThrowIfCancellationRequested(); + + string tryContentPath = work.Dequeue(); + ModelsRepositoryEventSource.Instance.FetchingModelContent(tryContentPath); + + try + { + string content = EvaluatePath(tryContentPath, cancellationToken); + return new FetchResult + { + Definition = content, + Path = tryContentPath + }; + } + catch (Exception) + { + remoteFetchError = + $"{string.Format(CultureInfo.InvariantCulture, StandardStrings.GenericResolverError, dtmi)} " + + string.Format(CultureInfo.InvariantCulture, StandardStrings.ErrorFetchingModelContent, tryContentPath); + } + } + + throw new RequestFailedException(remoteFetchError); + } + catch (Exception ex) + { + scope.Failed(ex); + throw; + } + } + + public async Task FetchAsync(string dtmi, Uri repositoryUri, CancellationToken cancellationToken = default) + { + using DiagnosticScope scope = _clientDiagnostics.CreateScope("RemoteModelFetcher.Fetch"); + scope.Start(); + try + { + Queue work = PrepareWork(dtmi, repositoryUri); + + string remoteFetchError = string.Empty; + RequestFailedException requestFailedExceptionThrown = null; + Exception genericExceptionThrown = null; + + while (work.Count != 0) + { + cancellationToken.ThrowIfCancellationRequested(); + + string tryContentPath = work.Dequeue(); + ModelsRepositoryEventSource.Instance.FetchingModelContent(tryContentPath); + + try + { + string content = await EvaluatePathAsync(tryContentPath, cancellationToken).ConfigureAwait(false); + return new FetchResult() + { + Definition = content, + Path = tryContentPath + }; + } + catch (RequestFailedException ex) + { + requestFailedExceptionThrown = ex; + } + catch (Exception ex) + { + genericExceptionThrown = ex; + } + + if (genericExceptionThrown != null || requestFailedExceptionThrown != null) + { + remoteFetchError = + $"{string.Format(CultureInfo.InvariantCulture, StandardStrings.GenericResolverError, dtmi)} " + + string.Format(CultureInfo.InvariantCulture, StandardStrings.ErrorFetchingModelContent, tryContentPath); + } + } + + if (requestFailedExceptionThrown != null) + { + throw new RequestFailedException( + requestFailedExceptionThrown.Status, + remoteFetchError, + requestFailedExceptionThrown.ErrorCode, + requestFailedExceptionThrown); + } + else + { + throw new RequestFailedException( + (int)HttpStatusCode.BadRequest, + remoteFetchError, + null, + genericExceptionThrown); + } + } + catch (Exception ex) + { + scope.Failed(ex); + throw; + } + } + + private Queue PrepareWork(string dtmi, Uri repositoryUri) + { + Queue work = new Queue(); + + if (_tryExpanded) + { + work.Enqueue(GetPath(dtmi, repositoryUri, true)); + } + + work.Enqueue(GetPath(dtmi, repositoryUri, false)); + + return work; + } + + private static string GetPath(string dtmi, Uri repositoryUri, bool expanded = false) + { + string absoluteUri = repositoryUri.AbsoluteUri; + return DtmiConventions.DtmiToQualifiedPath(dtmi, absoluteUri, expanded); + } + + private string EvaluatePath(string path, CancellationToken cancellationToken = default) + { + using DiagnosticScope scope = _clientDiagnostics.CreateScope("RemoteModelFetcher.EvaluatePath"); + scope.Start(); + + try + { + using HttpMessage message = CreateGetRequest(path); + + _pipeline.Send(message, cancellationToken); + + switch (message.Response.Status) + { + case 200: + { + return GetContent(message.Response.ContentStream); + } + default: + throw _clientDiagnostics.CreateRequestFailedException(message.Response); + } + } + catch (Exception ex) + { + scope.Failed(ex); + throw; + } + } + + private async Task EvaluatePathAsync(string path, CancellationToken cancellationToken = default) + { + using DiagnosticScope scope = _clientDiagnostics.CreateScope("RemoteModelFetcher.EvaluatePath"); + scope.Start(); + + try + { + using HttpMessage message = CreateGetRequest(path); + + await _pipeline.SendAsync(message, cancellationToken).ConfigureAwait(false); + + switch (message.Response.Status) + { + case 200: + { + return await GetContentAsync(message.Response.ContentStream, cancellationToken).ConfigureAwait(false); + } + default: + throw _clientDiagnostics.CreateRequestFailedException(message.Response); + } + } + catch (Exception ex) + { + scope.Failed(ex); + throw; + } + } + + private HttpMessage CreateGetRequest(string path) + { + HttpMessage message = _pipeline.CreateMessage(); + Request request = message.Request; + request.Method = RequestMethod.Get; + var uri = new RequestUriBuilder(); + uri.Reset(new Uri(path)); + request.Uri = uri; + + return message; + } + + private static string GetContent(Stream content) + { + using JsonDocument json = JsonDocument.Parse(content); + JsonElement root = json.RootElement; + return root.GetRawText(); + } + + private static async Task GetContentAsync(Stream content, CancellationToken cancellationToken) + { + using JsonDocument json = await JsonDocument.ParseAsync(content, default, cancellationToken).ConfigureAwait(false); + + JsonElement root = json.RootElement; + return root.GetRawText(); + } + + private static HttpPipeline CreatePipeline(ModelsRepositoryClientOptions options) + { + return HttpPipelineBuilder.Build(options); + } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelMetadata.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelMetadata.cs new file mode 100644 index 000000000000..08ad5a9f55e8 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelMetadata.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; + +namespace Azure.Iot.ModelsRepository +{ + /// + /// ModelMetadata is designated to store KPIs from model parsing. + /// + internal class ModelMetadata + { + public ModelMetadata(string id, IList extends, IList componentSchemas) + { + Id = id; + Extends = extends; + ComponentSchemas = componentSchemas; + } + + public string Id { get; } + public IList Extends { get; } + public IList ComponentSchemas { get; } + public IList Dependencies => Extends.Union(ComponentSchemas).ToList(); + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs new file mode 100644 index 000000000000..c6ab26c582f7 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelQuery.cs @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Text.Json; + +namespace Azure.Iot.ModelsRepository +{ + /// + /// The ModelQuery class is responsible for parsing DTDL v2 models to produce key metadata. + /// In the current form ModelQuery is focused on determining model dependencies recursively + /// via extends and component schemas. + /// + internal class ModelQuery + { + private readonly string _content; + private readonly JsonDocumentOptions _parseOptions; + + public ModelQuery(string content) + { + _content = content; + _parseOptions = new JsonDocumentOptions + { + AllowTrailingCommas = true + }; + } + + public ModelMetadata ParseModel() + { + using JsonDocument document = JsonDocument.Parse(_content, _parseOptions); + return ParseInterface(document.RootElement); + } + + private static ModelMetadata ParseInterface(JsonElement root) + { + string rootDtmi = ParseRootDtmi(root); + IList extends = ParseExtends(root); + IList contents = ParseContents(root); + + return new ModelMetadata(rootDtmi, extends, contents); + } + + private static string ParseRootDtmi(JsonElement root) + { + if (root.ValueKind == JsonValueKind.Object && root.TryGetProperty(ModelRepositoryConstants.ModelProperties.Dtmi, out JsonElement id)) + { + if (id.ValueKind == JsonValueKind.String) + { + return id.GetString(); + } + } + + return string.Empty; + } + + private static IList ParseExtends(JsonElement root) + { + var dependencies = new List(); + + if (root.ValueKind != JsonValueKind.Object) + { + return dependencies; + } + + if (!root.TryGetProperty(ModelRepositoryConstants.ModelProperties.Extends, out JsonElement extends)) + { + return dependencies; + } + + if (extends.ValueKind == JsonValueKind.String) + { + dependencies.Add(extends.GetString()); + } + else if (IsInterfaceObject(extends)) + { + ModelMetadata meta = ParseInterface(extends); + dependencies.AddRange(meta.Dependencies); + } + else if (extends.ValueKind == JsonValueKind.Array) + { + foreach (JsonElement extendElement in extends.EnumerateArray()) + { + if (extendElement.ValueKind == JsonValueKind.String) + { + dependencies.Add(extendElement.GetString()); + } + // Extends can have multiple levels and contain inline interfaces. + else if (IsInterfaceObject(extendElement)) + { + ModelMetadata meta = ParseInterface(extendElement); + dependencies.AddRange(meta.Dependencies); + } + } + } + + return dependencies; + } + + private static IList ParseContents(JsonElement root) + { + var dependencies = new List(); + + if (root.ValueKind != JsonValueKind.Object) + { + return dependencies; + } + + if (!root.TryGetProperty(ModelRepositoryConstants.ModelProperties.Contents, out JsonElement contents)) + { + return dependencies; + } + + if (contents.ValueKind != JsonValueKind.Array) + { + return dependencies; + } + + foreach (JsonElement contentElement in contents.EnumerateArray()) + { + if (IsComponentObject(contentElement)) + { + dependencies.AddRange(ParseComponent(contentElement)); + } + } + + return dependencies; + } + + private static IList ParseComponent(JsonElement root) + { + // We already know root is an object of @type Component + + var dependencies = new List(); + + if (!root.TryGetProperty(ModelRepositoryConstants.ModelProperties.Schema, out JsonElement componentSchema)) + { + return dependencies; + } + + if (componentSchema.ValueKind == JsonValueKind.String) + { + dependencies.Add(componentSchema.GetString()); + } + else if (IsInterfaceObject(componentSchema)) + { + ModelMetadata meta = ParseInterface(componentSchema); + dependencies.AddRange(meta.Dependencies); + } + else if (componentSchema.ValueKind == JsonValueKind.Array) + { + foreach (JsonElement componentSchemaElement in componentSchema.EnumerateArray()) + { + if (componentSchemaElement.ValueKind == JsonValueKind.String) + { + dependencies.Add(componentSchemaElement.GetString()); + } + else if (IsInterfaceObject(componentSchemaElement)) + { + ModelMetadata meta = ParseInterface(componentSchemaElement); + dependencies.AddRange(meta.Dependencies); + } + } + } + + return dependencies; + } + + private static bool IsInterfaceObject(JsonElement root) + { + return root.ValueKind == JsonValueKind.Object && + root.TryGetProperty(ModelRepositoryConstants.ModelProperties.Type, out JsonElement objectType) && + objectType.ValueKind == JsonValueKind.String && + objectType.GetString() == ModelRepositoryConstants.ModelProperties.TypeValueInterface; + } + + private static bool IsComponentObject(JsonElement root) + { + return root.ValueKind == JsonValueKind.Object && + root.TryGetProperty(ModelRepositoryConstants.ModelProperties.Type, out JsonElement objectType) && + objectType.ValueKind == JsonValueKind.String && + objectType.GetString() == ModelRepositoryConstants.ModelProperties.TypeValueComponent; + } + + public Dictionary ListToDict() + { + Dictionary result = new Dictionary(); + + using JsonDocument document = JsonDocument.Parse(_content, _parseOptions); + JsonElement _root = document.RootElement; + + if (_root.ValueKind == JsonValueKind.Array) + { + foreach (JsonElement element in _root.EnumerateArray()) + { + if (element.ValueKind == JsonValueKind.Object) + { + using MemoryStream stream = WriteJsonElementToStream(element); + + using StreamReader streamReader = new StreamReader(stream); + string serialized = streamReader.ReadToEnd(); + + string id = new ModelQuery(serialized).ParseModel().Id; + result.Add(id, serialized); + } + } + } + + return result; + } + + private static MemoryStream WriteJsonElementToStream(JsonElement item) + { + var memoryStream = new MemoryStream(); + using var writer = new Utf8JsonWriter(memoryStream); + + item.WriteTo(writer); + writer.Flush(); + memoryStream.Seek(0, SeekOrigin.Begin); + + return memoryStream; + } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs new file mode 100644 index 000000000000..9db131f502a9 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelRepositoryConstants.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.Iot.ModelsRepository +{ + internal static class ModelRepositoryConstants + { + // Set EventSource name to package name replacing '.' with '-' + public const string ModelRepositoryEventSourceName = "Azure-Iot-ModelsRepository"; + + public const string DefaultModelsRepository = "https://devicemodels.azure.com"; + + // File Extensions + public const string JsonFileExtension = ".json"; + public const string ExpandedJsonFileExtension = ".expanded.json"; + public const string File = "file"; + + /// + /// The ModelRepositoryConstants.ModelProperties class contains DTDL v2 property names and property values + /// used by the ModelQuery class to parse DTDL model key indicators. + /// + internal static class ModelProperties + { + public const string Dtmi = "@id"; + public const string Type = "@type"; + public const string Extends = "extends"; + public const string Contents = "contents"; + public const string Schema = "schema"; + public const string TypeValueInterface = "Interface"; + public const string TypeValueComponent = "Component"; + } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelsRepositoryClient.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelsRepositoryClient.cs new file mode 100644 index 000000000000..72d157aaeb9c --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelsRepositoryClient.cs @@ -0,0 +1,195 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core.Pipeline; + +namespace Azure.Iot.ModelsRepository +{ + /// + /// The ModelsRepositoryClient class supports operations against DTDL model repositories following the + /// conventions of the Azure IoT Plug and Play Models repository. + /// + public class ModelsRepositoryClient + { + private readonly RepositoryHandler _repositoryHandler; + private readonly ClientDiagnostics _clientDiagnostics; + private readonly ModelsRepositoryClientOptions _clientOptions; + + /// + /// Initializes the ModelsRepositoryClient with default client options while pointing to + /// the Azure IoT Plug and Play Models repository https://devicemodels.azure.com for resolution. + /// + public ModelsRepositoryClient() : this(DefaultModelsRepository, new ModelsRepositoryClientOptions()) { } + + /// + /// Initializes the ModelsRepositoryClient with custom client while pointing to + /// the Azure IoT Plug and Play Model repository https://devicemodels.azure.com for resolution. + /// + /// + /// ModelsRepositoryClientOptions to configure resolution and client behavior. + /// + public ModelsRepositoryClient(ModelsRepositoryClientOptions options) : this(DefaultModelsRepository, options) { } + + /// + /// Initializes the ModelsRepositoryClient with custom client while pointing to + /// a custom for resolution. + /// + /// + /// The model repository Uri. This can be a remote endpoint or local directory. + /// + /// + /// ModelsRepositoryClientOptions to configure resolution and client behavior. + /// + public ModelsRepositoryClient(Uri repositoryUri, ModelsRepositoryClientOptions options = default) + { + if (options == null) + { + options = new ModelsRepositoryClientOptions(); + } + + RepositoryUri = repositoryUri; + _clientOptions = options; + _clientDiagnostics = new ClientDiagnostics(options); + _repositoryHandler = new RepositoryHandler(RepositoryUri, _clientDiagnostics, _clientOptions); + } + + /// + /// Resolves a model definition identified by and optionally its dependencies. + /// + /// + /// An IDictionary containing the model definition(s) where the key is the dtmi + /// and the value is the raw model definition string. + /// + /// Thrown when a resolution failure occurs. + /// A well-formed DTDL model Id. For example 'dtmi:com:example:Thermostat;1'. + /// The cancellationToken. + [SuppressMessage( + "Usage", + "AZC0015:Unexpected client method return type.", + Justification = "Item lookup is optimized with a dictionary type, we do not expect any more than ~20 items to be returned. TODO: azabbasi: discuss this issue with the review board.")] + public virtual async Task> ResolveAsync(string dtmi, CancellationToken cancellationToken = default) + { + using DiagnosticScope scope = _clientDiagnostics.CreateScope($"{nameof(ModelsRepositoryClient)}.{nameof(Resolve)}"); + scope.Start(); + try + { + return await _repositoryHandler.ProcessAsync(dtmi, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + scope.Failed(ex); + throw; + } + } + + /// + /// Resolves a model definition identified by and optionally its dependencies. + /// + /// + /// An IDictionary containing the model definition(s) where the key is the dtmi + /// and the value is the raw model definition string. + /// + /// Thrown when a resolution failure occurs. + /// A well-formed DTDL model Id. For example 'dtmi:com:example:Thermostat;1'. + /// The cancellationToken. + [SuppressMessage( + "Usage", + "AZC0015:Unexpected client method return type.", + Justification = "Item lookup is optimized with a dictionary type, we do not expect any more than ~20 items to be returned. TODO: azabbasi: discuss this issue with the review board.")] + public virtual IDictionary Resolve(string dtmi, CancellationToken cancellationToken = default) + { + using DiagnosticScope scope = _clientDiagnostics.CreateScope($"{nameof(ModelsRepositoryClient)}.{nameof(Resolve)}"); + scope.Start(); + + try + { + return _repositoryHandler.Process(dtmi, cancellationToken); + } + catch (Exception ex) + { + scope.Failed(ex); + throw; + } + } + + /// + /// Resolves a collection of model definitions identified by and optionally their dependencies. + /// + /// + /// An IDictionary containing the model definition(s) where the key is the dtmi + /// and the value is the raw model definition string. + /// + /// Thrown when a resolution failure occurs. + /// A collection of well-formed DTDL model Ids. + /// The cancellationToken. + [SuppressMessage( + "Usage", + "AZC0015:Unexpected client method return type.", + Justification = "Item lookup is optimized with a dictionary type, we do not expect any more than ~20 items to be returned. TODO: azabbasi: discuss this issue with the review board.")] + public virtual async Task> ResolveAsync(IEnumerable dtmis, CancellationToken cancellationToken = default) + { + using DiagnosticScope scope = _clientDiagnostics.CreateScope($"{nameof(ModelsRepositoryClient)}.{nameof(Resolve)}"); + scope.Start(); + + try + { + return await _repositoryHandler.ProcessAsync(dtmis, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + scope.Failed(ex); + throw; + } + } + + /// + /// Resolves a collection of model definitions identified by and optionally their dependencies. + /// + /// + /// An IDictionary containing the model definition(s) where the key is the dtmi + /// and the value is the raw model definition string. + /// + /// Thrown when a resolution failure occurs. + /// A collection of well-formed DTDL model Ids. + /// The cancellationToken. + [SuppressMessage( + "Usage", + "AZC0015:Unexpected client method return type.", + Justification = "Item lookup is optimized with a dictionary type, we do not expect any more than ~20 items to be returned. TODO: azabbasi: discuss this issue with the review board.")] + public virtual IDictionary Resolve(IEnumerable dtmis, CancellationToken cancellationToken = default) + { + using DiagnosticScope scope = _clientDiagnostics.CreateScope($"{nameof(ModelsRepositoryClient)}.{nameof(Resolve)}"); + scope.Start(); + + try + { + return _repositoryHandler.Process(dtmis, cancellationToken); + } + catch (Exception ex) + { + scope.Failed(ex); + throw; + } + } + + /// + /// Evaluates whether an input is valid. + /// + public static bool IsValidDtmi(string dtmi) => DtmiConventions.IsDtmi(dtmi); + + /// + /// Gets the Uri associated with the ModelsRepositoryClient instance. + /// + public Uri RepositoryUri { get; } + + /// + /// The global Azure IoT Models Repository endpoint used by default. + /// + public static Uri DefaultModelsRepository => new Uri(ModelRepositoryConstants.DefaultModelsRepository); + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelsRepositoryClientOptions.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelsRepositoryClientOptions.cs new file mode 100644 index 000000000000..1382f075dcc0 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelsRepositoryClientOptions.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using Azure.Core; + +namespace Azure.Iot.ModelsRepository +{ + /// + /// Options that allow configuration of requests sent to the ModelRepositoryService. + /// + public class ModelsRepositoryClientOptions : ClientOptions + { + internal const ServiceVersion LatestVersion = ServiceVersion.V2021_02_11; + + /// + /// The versions of Azure IoT Model Repository by this client + /// library. + /// + public enum ServiceVersion + { + /// + /// 2021_02_11 + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "")] + V2021_02_11 = 1 + } + + /// + /// Gets the of the service API used when + /// making requests. + /// + public ServiceVersion Version { get; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The of the service API used when + /// making requests. + /// + /// The dependency processing options. + public ModelsRepositoryClientOptions( + ServiceVersion version = LatestVersion, + DependencyResolutionOption resolutionOption = DependencyResolutionOption.Enabled) + { + DependencyResolution = resolutionOption; + Version = version; + } + + /// + /// The dependency processing options. + /// + public DependencyResolutionOption DependencyResolution { get; } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelsRepositoryEventSource.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelsRepositoryEventSource.cs new file mode 100644 index 000000000000..db002ef02223 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/ModelsRepositoryEventSource.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core.Diagnostics; +using System; +using System.Diagnostics.Tracing; + +namespace Azure.Iot.ModelsRepository +{ + [EventSource(Name = EventSourceName)] + internal sealed class ModelsRepositoryEventSource : EventSource + { + private const string EventSourceName = ModelRepositoryConstants.ModelRepositoryEventSourceName; + + // Event ids defined as constants to makes it easy to keep track of them + // Consider EventSource name, Guid, Event Id and parameters as public API and follow the appropriate versioning rules. + // More information on EventSource and Azure guidelines: + // https://azure.github.io/azure-sdk/dotnet_implementation.html#eventsource + + private const int InitFetcherEventId = 1000; + private const int ProcessingDtmiEventId = 2000; + private const int FetchingModelContentEventId = 2001; + private const int DiscoveredDependenciesEventId = 2002; + private const int SkippingPreprocessedDtmiEventId = 2003; + private const int InvalidDtmiInputEventId = 4000; + private const int ErrorFetchingModelContentEventId = 4004; + private const int IncorrectDtmiCasingEventId = 4006; + + public static ModelsRepositoryEventSource Instance { get; } = new ModelsRepositoryEventSource(); + + private ModelsRepositoryEventSource() + : base(EventSourceName, + EventSourceSettings.Default, + AzureEventSourceListener.TraitName, + AzureEventSourceListener.TraitValue) + { } + + [Event(InitFetcherEventId, Level = EventLevel.Informational, Message = StandardStrings.ClientInitWithFetcher)] + public void InitFetcher(Guid clientId, string scheme) + { + // We are calling Guid.ToString make sure anyone is listening before spending resources + if (IsEnabled(EventLevel.Informational, EventKeywords.None)) + { + WriteEvent(InitFetcherEventId, clientId.ToString("N"), scheme); + } + } + + [Event(InvalidDtmiInputEventId, Level = EventLevel.Error, Message = StandardStrings.InvalidDtmiFormat)] + public void InvalidDtmiInput(string dtmi) + { + if (IsEnabled(EventLevel.Error, EventKeywords.None)) + { + WriteEvent(InvalidDtmiInputEventId, dtmi); + } + } + + [Event(SkippingPreprocessedDtmiEventId, Level = EventLevel.Informational, Message = StandardStrings.SkippingPreProcessedDtmi)] + public void SkippingPreprocessedDtmi(string dtmi) + { + if (IsEnabled(EventLevel.Informational, EventKeywords.None)) + { + WriteEvent(SkippingPreprocessedDtmiEventId, dtmi); + } + } + + [Event(ProcessingDtmiEventId, Level = EventLevel.Informational, Message = StandardStrings.ProcessingDtmi)] + public void ProcessingDtmi(string dtmi) + { + if (IsEnabled(EventLevel.Informational, EventKeywords.None)) + { + WriteEvent(ProcessingDtmiEventId, dtmi); + } + } + + [Event(DiscoveredDependenciesEventId, Level = EventLevel.Informational, Message = StandardStrings.DiscoveredDependencies)] + public void DiscoveredDependencies(string dependencies) + { + if (IsEnabled(EventLevel.Informational, EventKeywords.None)) + { + WriteEvent(DiscoveredDependenciesEventId, dependencies); + } + } + + [Event(IncorrectDtmiCasingEventId, Level = EventLevel.Error, Message = StandardStrings.IncorrectDtmiCasing)] + public void IncorrectDtmiCasing(string expected, string parsed) + { + if (IsEnabled(EventLevel.Error, EventKeywords.None)) + { + WriteEvent(IncorrectDtmiCasingEventId, expected, parsed); + } + } + + [Event(FetchingModelContentEventId, Level = EventLevel.Informational, Message = StandardStrings.FetchingModelContent)] + public void FetchingModelContent(string path) + { + if (IsEnabled(EventLevel.Informational, EventKeywords.None)) + { + WriteEvent(FetchingModelContentEventId, path); + } + } + + [Event(ErrorFetchingModelContentEventId, Level = EventLevel.Error, Message = StandardStrings.ErrorFetchingModelContent)] + public void ErrorFetchingModelContent(string path) + { + if (IsEnabled(EventLevel.Error, EventKeywords.None)) + { + WriteEvent(ErrorFetchingModelContentEventId, path); + } + } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Properties/AssemblyInfo.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Properties/AssemblyInfo.cs new file mode 100644 index 000000000000..c310411f819f --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Azure.Iot.ModelsRepository.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d15ddcb29688295338af4b7686603fe614abd555e09efba8fb88ee09e1f7b1ccaeed2e8f823fa9eef3fdd60217fc012ea67d2479751a0b8c087a4185541b851bd8b16f8d91b840e51b1cb0ba6fe647997e57429265e85ef62d565db50a69ae1647d54d7bd855e4db3d8a91510e5bcbd0edfbbecaa20a7bd9ae74593daa7b11b4")] +[assembly: Azure.Core.AzureResourceProviderNamespace("Azure.Iot.ModelsRepository")] diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs new file mode 100644 index 000000000000..d7bdf5d15716 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/RepositoryHandler.cs @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core; +using Azure.Core.Pipeline; +using Azure.Iot.ModelsRepository.Fetchers; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; + +namespace Azure.Iot.ModelsRepository +{ + internal class RepositoryHandler + { + private readonly IModelFetcher _modelFetcher; + private readonly Guid _clientId; + private readonly ClientDiagnostics _clientDiagnostics; + private readonly Uri _repositoryUri; + private readonly ModelsRepositoryClientOptions _clientOptions; + + public RepositoryHandler(Uri repositoryUri, ClientDiagnostics clientDiagnostics, ModelsRepositoryClientOptions options) + { + Argument.AssertNotNull(options, nameof(options)); + + _clientOptions = options; + _clientDiagnostics = clientDiagnostics; + _modelFetcher = repositoryUri.Scheme == ModelRepositoryConstants.File + ? _modelFetcher = new LocalModelFetcher(_clientDiagnostics, _clientOptions) + : _modelFetcher = new RemoteModelFetcher(_clientDiagnostics, _clientOptions); + _clientId = Guid.NewGuid(); + + _repositoryUri = repositoryUri; + + ModelsRepositoryEventSource.Instance.InitFetcher(_clientId, repositoryUri.Scheme); + } + + public async Task> ProcessAsync(string dtmi, CancellationToken cancellationToken) + { + return await ProcessAsync(new List { dtmi }, true, cancellationToken).ConfigureAwait(false); + } + + public IDictionary Process(string dtmi, CancellationToken cancellationToken) + { + return ProcessAsync(new List { dtmi }, false, cancellationToken).EnsureCompleted(); + } + + public Task> ProcessAsync(IEnumerable dtmis, CancellationToken cancellationToken) + { + return ProcessAsync(dtmis, true, cancellationToken); + } + + public IDictionary Process(IEnumerable dtmis, CancellationToken cancellationToken) + { + return ProcessAsync(dtmis, false, cancellationToken).EnsureCompleted(); + } + + private async Task> ProcessAsync(IEnumerable dtmis, bool async, CancellationToken cancellationToken) + { + var processedModels = new Dictionary(); + Queue toProcessModels = PrepareWork(dtmis); + + while (toProcessModels.Count != 0) + { + cancellationToken.ThrowIfCancellationRequested(); + + string targetDtmi = toProcessModels.Dequeue(); + if (processedModels.ContainsKey(targetDtmi)) + { + ModelsRepositoryEventSource.Instance.SkippingPreprocessedDtmi(targetDtmi); + continue; + } + + ModelsRepositoryEventSource.Instance.ProcessingDtmi(targetDtmi); + + FetchResult result = async + ? await FetchAsync(targetDtmi, cancellationToken).ConfigureAwait(false) + : Fetch(targetDtmi, cancellationToken); + + if (result.FromExpanded) + { + Dictionary expanded = new ModelQuery(result.Definition).ListToDict(); + + foreach (KeyValuePair kvp in expanded) + { + if (!processedModels.ContainsKey(kvp.Key)) + { + processedModels.Add(kvp.Key, kvp.Value); + } + } + + continue; + } + + ModelMetadata metadata = new ModelQuery(result.Definition).ParseModel(); + + if (_clientOptions.DependencyResolution >= DependencyResolutionOption.Enabled) + { + IList dependencies = metadata.Dependencies; + + if (dependencies.Count > 0) + { + ModelsRepositoryEventSource.Instance.DiscoveredDependencies(string.Join("\", \"", dependencies)); + } + + foreach (string dep in dependencies) + { + toProcessModels.Enqueue(dep); + } + } + + string parsedDtmi = metadata.Id; + if (!parsedDtmi.Equals(targetDtmi, StringComparison.Ordinal)) + { + ModelsRepositoryEventSource.Instance.IncorrectDtmiCasing(targetDtmi, parsedDtmi); + string formatErrorMsg = + $"{string.Format(CultureInfo.InvariantCulture, StandardStrings.GenericResolverError, targetDtmi)} " + + string.Format(CultureInfo.InvariantCulture, StandardStrings.IncorrectDtmiCasing, targetDtmi, parsedDtmi); + + throw new RequestFailedException(formatErrorMsg, new FormatException(formatErrorMsg)); + } + + processedModels.Add(targetDtmi, result.Definition); + } + + return processedModels; + } + + private Task FetchAsync(string dtmi, CancellationToken cancellationToken) + { + return _modelFetcher.FetchAsync(dtmi, _repositoryUri, cancellationToken); + } + + private FetchResult Fetch(string dtmi, CancellationToken cancellationToken) + { + return _modelFetcher.Fetch(dtmi, _repositoryUri, cancellationToken); + } + + private static Queue PrepareWork(IEnumerable dtmis) + { + var toProcessModels = new Queue(); + foreach (string dtmi in dtmis) + { + if (!DtmiConventions.IsDtmi(dtmi)) + { + ModelsRepositoryEventSource.Instance.InvalidDtmiInput(dtmi); + + string invalidArgMsg = + $"{string.Format(CultureInfo.InvariantCulture, StandardStrings.GenericResolverError, dtmi)} " + + string.Format(CultureInfo.InvariantCulture, StandardStrings.InvalidDtmiFormat, dtmi); + + throw new RequestFailedException(invalidArgMsg, new ArgumentException(invalidArgMsg)); + } + + toProcessModels.Enqueue(dtmi); + } + + return toProcessModels; + } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/StandardStrings.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/StandardStrings.cs new file mode 100644 index 000000000000..24bc475ee9c2 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/src/StandardStrings.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.Iot.ModelsRepository +{ + internal class StandardStrings + { + public const string GenericResolverError = "Unable to resolve \"{0}\"."; + public const string InvalidDtmiFormat = "Invalid DTMI format \"{0}\"."; + public const string ClientInitWithFetcher = "Client session {0} initialized with {1} content fetcher."; + public const string ProcessingDtmi = "Processing DTMI \"{0}\". "; + public const string SkippingPreProcessedDtmi = "Already processed DTMI \"{0}\". Skipping."; + public const string DiscoveredDependencies = "Discovered dependencies \"{0}\"."; + public const string FetchingModelContent = "Attempting to retrieve model content from \"{0}\"."; + public const string ErrorFetchingModelContent = "Model file \"{0}\" not found or not accessible in target repository."; + public const string IncorrectDtmiCasing = + "Retrieved model has incorrect DTMI casing. Expected \"{0}\", parsed \"{1}\"."; + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/Azure.Iot.ModelsRepository.Tests.csproj b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/Azure.Iot.ModelsRepository.Tests.csproj new file mode 100644 index 000000000000..3142e95ed80e --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/Azure.Iot.ModelsRepository.Tests.csproj @@ -0,0 +1,35 @@ + + + + $(RequiredTargetFrameworks) + $(DefineConstants);TESTFRAMEWORK + true + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + + + + + + diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs new file mode 100644 index 000000000000..326f2d96fd4f --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ClientTests.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using FluentAssertions; +using NUnit.Framework; +using System; +using System.Diagnostics.Tracing; +using System.Runtime.InteropServices; + +namespace Azure.Iot.ModelsRepository.Tests +{ + public class ClientTests : ModelsRepositoryTestBase + { + [Test] + public void CtorOverloads() + { + string remoteUriStr = "https://dtmi.com"; + Uri remoteUri = new Uri(remoteUriStr); + + new ModelsRepositoryClient().RepositoryUri.Should().Be(ModelsRepositoryClient.DefaultModelsRepository); + new ModelsRepositoryClient(remoteUri).RepositoryUri.Should().Be(remoteUri); + + string localUriStr = TestLocalModelRepository; + + var localUri = new Uri(localUriStr); + new ModelsRepositoryClient(localUri).RepositoryUri.Should().Be(localUri); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + localUriStr = localUriStr.Replace("\\", "/"); + } + + new ModelsRepositoryClient(localUri).RepositoryUri.AbsolutePath.Should().Be(localUriStr); + } + + [TestCase("dtmi:com:example:Thermostat;1", true)] + [TestCase("dtmi:contoso:scope:entity;2", true)] + [TestCase("dtmi:com:example:Thermostat:1", false)] + [TestCase("dtmi:com:example::Thermostat;1", false)] + [TestCase("com:example:Thermostat;1", false)] + [TestCase("", false)] + [TestCase(null, false)] + public void ClientIsValidDtmi(string dtmi, bool expected) + { + ModelsRepositoryClient.IsValidDtmi(dtmi).Should().Be(expected); + } + + [Test] + public void EvaluateEventSourceKPIs() + { + Type eventSourceType = typeof(ModelsRepositoryEventSource); + + eventSourceType.Should().NotBeNull(); + EventSource.GetName(eventSourceType).Should().Be(ModelRepositoryConstants.ModelRepositoryEventSourceName); + EventSource.GetGuid(eventSourceType).Should().Be(Guid.Parse("7678f8d4-81db-5fd2-39fc-23552d86b171")); + EventSource.GenerateManifest(eventSourceType, "assemblyPathToIncludeInManifest").Should().NotBeNullOrEmpty(); + } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/DtmiConversionTests.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/DtmiConversionTests.cs new file mode 100644 index 000000000000..99dff1d02387 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/DtmiConversionTests.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using FluentAssertions; +using NUnit.Framework; +using System; +using System.Runtime.InteropServices; + +namespace Azure.Iot.ModelsRepository.Tests +{ + public class DtmiConversionTests : ModelsRepositoryTestBase + { + [TestCase("dtmi:com:Example:Model;1", "dtmi/com/example/model-1.json")] + [TestCase("dtmi:com:example:Model;1", "dtmi/com/example/model-1.json")] + [TestCase("dtmi:com:example:Model:1", null)] + [TestCase("", null)] + [TestCase(null, null)] + public void DtmiToPath(string dtmi, string expectedPath) + { + DtmiConventions.DtmiToPath(dtmi).Should().Be(expectedPath); + } + + [TestCase("dtmi:com:example:Thermostat;1", "dtmi/com/example/thermostat-1.json", "https://localhost/repository")] + [TestCase("dtmi:com:example:Thermostat;1", "dtmi/com/example/thermostat-1.json", @"C:\fakeRegistry")] + [TestCase("dtmi:com:example:Thermostat;1", "dtmi/com/example/thermostat-1.json", "/me/fakeRegistry")] + [TestCase("dtmi:com:example:Thermostat:1", null, "https://localhost/repository")] + [TestCase("dtmi:com:example:Thermostat:1", null, "/me/fakeRegistry")] + public void DtmiToQualifiedPath(string dtmi, string expectedPath, string repository) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + repository = repository.Replace("\\", "/"); + } + + if (string.IsNullOrEmpty(expectedPath)) + { + Action act = () => DtmiConventions.DtmiToQualifiedPath(dtmi, repository); + act.Should().Throw().WithMessage(string.Format(StandardStrings.InvalidDtmiFormat, dtmi)); + return; + } + + string modelPath = DtmiConventions.DtmiToQualifiedPath(dtmi, repository); + modelPath.Should().Be($"{repository}/{expectedPath}"); + + string expandedModelPath = DtmiConventions.DtmiToQualifiedPath(dtmi, repository, true); + expandedModelPath + .Should() + .Be($"{repository}/{expectedPath.Replace(ModelRepositoryConstants.JsonFileExtension, ModelRepositoryConstants.ExpandedJsonFileExtension)}"); + } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelQueryTests.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelQueryTests.cs new file mode 100644 index 000000000000..9c2dbae7dcb1 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelQueryTests.cs @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using FluentAssertions; +using NUnit.Framework; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Azure.Iot.ModelsRepository.Tests +{ + public class ModelQueryTests : ModelsRepositoryTestBase + { + private readonly string _modelTemplate = @"{{ + {0} + ""@type"": ""Interface"", + ""displayName"": ""Phone"", + {1} + {2} + ""@context"": ""dtmi:dtdl:context;2"" + }}"; + + [TestCase("\"@id\": \"dtmi:com:example:thermostat;1\",", "dtmi:com:example:thermostat;1")] + [TestCase("\"@id\": \"\",", "")] + [TestCase("", "")] + public void GetId(string formatId, string expectedId) + { + string modelContent = string.Format(_modelTemplate, formatId, "", ""); + ModelQuery query = new ModelQuery(modelContent); + query.ParseModel().Id.Should().Be(expectedId); + } + + [TestCase( + @" + ""contents"": + [{ + ""@type"": ""Property"", + ""name"": ""capacity"", + ""schema"": ""integer"" + }, + { + ""@type"": ""Component"", + ""name"": ""frontCamera"", + ""schema"": ""dtmi:com:example:Camera;3"" + }, + { + ""@type"": ""Component"", + ""name"": ""backCamera"", + ""schema"": ""dtmi:com:example:Camera;3"" + }, + { + ""@type"": ""Component"", + ""name"": ""deviceInfo"", + ""schema"": ""dtmi:azure:DeviceManagement:DeviceInformation;1"" + }],", + "dtmi:com:example:Camera;3,dtmi:com:example:Camera;3,dtmi:azure:DeviceManagement:DeviceInformation;1" + )] + [TestCase( + @" + ""contents"": + [{ + ""@type"": ""Property"", + ""name"": ""capacity"", + ""schema"": ""integer"" + }],", "" + )] + [TestCase(@"""contents"":[],", "")] + [TestCase("", "")] + public void GetComponentSchema(string contents, string expected) + { + string[] expectedDtmis = expected.Split(new[] { "," }, System.StringSplitOptions.RemoveEmptyEntries); + string modelContent = string.Format(_modelTemplate, "", "", contents); + ModelQuery query = new ModelQuery(modelContent); + IList componentSchemas = query.ParseModel().ComponentSchemas; + componentSchemas.Count.Should().Be(expectedDtmis.Length); + + foreach (string schema in componentSchemas) + { + expectedDtmis.Should().Contain(schema); + } + } + + [TestCase( + "\"extends\": [\"dtmi:com:example:Camera;3\",\"dtmi:azure:DeviceManagement:DeviceInformation;1\"],", + "dtmi:com:example:Camera;3,dtmi:azure:DeviceManagement:DeviceInformation;1" + )] + [TestCase("\"extends\": [],", "")] + [TestCase("\"extends\": \"dtmi:com:example:Camera;3\",", "dtmi:com:example:Camera;3")] + [TestCase("", "")] + public void GetExtends(string extends, string expected) + { + string[] expectedDtmis = expected.Split(new[] { "," }, System.StringSplitOptions.RemoveEmptyEntries); + string modelContent = string.Format(_modelTemplate, "", extends, ""); + ModelQuery query = new ModelQuery(modelContent); + IList extendsDtmis = query.ParseModel().Extends; + extendsDtmis.Count.Should().Be(expectedDtmis.Length); + + foreach (string dtmi in extendsDtmis) + { + expectedDtmis.Should().Contain(dtmi); + } + } + + [TestCase( + "\"@id\": \"dtmi:com:example:thermostat;1\",", + "\"extends\": [\"dtmi:com:example:Camera;3\",\"dtmi:azure:DeviceManagement:DeviceInformation;1\"],", + @"""contents"": + [{ + ""@type"": ""Property"", + ""name"": ""capacity"", + ""schema"": ""integer"" + }, + { + ""@type"": ""Component"", + ""name"": ""frontCamera"", + ""schema"": ""dtmi:com:example:Camera;3"" + }, + { + ""@type"": ""Component"", + ""name"": ""backCamera"", + ""schema"": ""dtmi:com:example:Camera;3"" + }],", + "dtmi:com:example:Camera;3,dtmi:azure:DeviceManagement:DeviceInformation;1" + ), + TestCase( + "\"@id\": \"dtmi:example:Interface1;1\",", + @"""extends"": [""dtmi:example:Interface2;1"", { + ""@id"": ""dtmi:example:Interface3;1"", + ""@type"": ""Interface"", + ""contents"": [{ + ""@type"": ""Component"", + ""name"": ""comp1"", + ""schema"": [""dtmi:example:Interface4;1""] + }, + { + ""@type"": ""Component"", + ""name"": ""comp2"", + ""schema"": { + ""@id"": ""dtmi:example:Interface5;1"", + ""@type"": ""Interface"", + ""extends"": ""dtmi:example:Interface6;1"" + } + } + ] + }],", + "", + "dtmi:example:Interface2;1,dtmi:example:Interface4;1,dtmi:example:Interface6;1" + )] + public void GetModelDependencies(string id, string extends, string contents, string expected) + { + string[] expectedDtmis = expected.Split(new[] { "," }, System.StringSplitOptions.RemoveEmptyEntries); + string modelContent = string.Format(_modelTemplate, id, extends, contents); + ModelMetadata metadata = new ModelQuery(modelContent).ParseModel(); + + IList dependencies = metadata.Dependencies; + dependencies.Count.Should().Be(expectedDtmis.Length); + + foreach (string dtmi in dependencies) + { + expectedDtmis.Should().Contain(dtmi); + } + } + + [Test] + public void ListToDict() + { + string testRepoPath = TestLocalModelRepository; + string expandedContent = File.ReadAllText( + $"{testRepoPath}/dtmi/com/example/temperaturecontroller-1.expanded.json", Encoding.UTF8); + ModelQuery query = new ModelQuery(expandedContent); + Dictionary transformResult = query.ListToDict(); + + // Assert KPIs for TemperatureController;1. + // Ensure transform of expanded content to dictionary is what we'd expect. + string[] expectedIds = new string[] { + "dtmi:azure:DeviceManagement:DeviceInformation;1", + "dtmi:com:example:Thermostat;1", + "dtmi:com:example:TemperatureController;1" }; + + transformResult.Keys.Count.Should().Be(expectedIds.Length); + + foreach (string id in expectedIds) + { + transformResult.Should().ContainKey(id); + ParseRootDtmiFromJson(transformResult[id]).Should().Be(id); + } + } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelsRepositoryRecordedTestBase.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelsRepositoryRecordedTestBase.cs new file mode 100644 index 000000000000..ca7e91638c40 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelsRepositoryRecordedTestBase.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Net; +using Azure.Core.TestFramework; +using NUnit.Framework; + +namespace Azure.Iot.ModelsRepository.Tests +{ + public class ModelsRepositoryRecordedTestBase : RecordedTestBase + { + protected const string TestModeEnvVariable = "AZURE_TEST_MODE"; + + protected static RecordedTestMode TestMode => (RecordedTestMode)Enum.Parse( + typeof(RecordedTestMode), + Environment.GetEnvironmentVariable(TestModeEnvVariable) ?? "Playback"); + + public ModelsRepositoryRecordedTestBase(bool isAsync) : base(isAsync, TestMode) + { + } + + [SetUp] + public virtual void SetupE2eTestBase() + { + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + } + + protected ModelsRepositoryClient GetClient(ModelsRepositoryTestBase.ClientType clientType, ModelsRepositoryClientOptions options = default) + { + if (options == null) + { + options = new ModelsRepositoryClientOptions(); + } + + return + clientType == ModelsRepositoryTestBase.ClientType.Local + ? InstrumentClient( + new ModelsRepositoryClient( + new Uri(ModelsRepositoryTestBase.TestLocalModelRepository), + InstrumentClientOptions(options))) + : InstrumentClient( + new ModelsRepositoryClient( + new Uri(ModelsRepositoryTestBase.TestRemoteModelRepository), + InstrumentClientOptions(options))); + } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelsRepositoryTestBase.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelsRepositoryTestBase.cs new file mode 100644 index 000000000000..b7409eeb8315 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelsRepositoryTestBase.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Reflection; +using System.Text.Json; + +namespace Azure.Iot.ModelsRepository.Tests +{ + /// + /// This class will initialize all the settings and create and instance of the ModelsRepoClient. + /// + public abstract class ModelsRepositoryTestBase + { + public ModelsRepositoryTestBase() + { + } + + public static string ParseRootDtmiFromJson(string json) + { + var options = new JsonDocumentOptions + { + AllowTrailingCommas = true + }; + + string dtmi = string.Empty; + using (JsonDocument document = JsonDocument.Parse(json, options)) + { + dtmi = document.RootElement.GetProperty("@id").GetString(); + } + return dtmi; + } + + public static readonly string FallbackTestRemoteRepo = ModelRepositoryConstants.DefaultModelsRepository; + + public static string TestDirectoryPath => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + public static string TestLocalModelRepository => Path.Combine(TestDirectoryPath, "TestModelRepo"); + + public static string TestRemoteModelRepository => Environment.GetEnvironmentVariable("PNP_TEST_REMOTE_REPO") ?? FallbackTestRemoteRepo; + + public enum ClientType + { + Local, + Remote + } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelsRepositoryTestEnvironment.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelsRepositoryTestEnvironment.cs new file mode 100644 index 000000000000..747bf0375669 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ModelsRepositoryTestEnvironment.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core.TestFramework; + +namespace Azure.Iot.ModelsRepository.Tests +{ + public class ModelsRepositoryTestEnvironment : TestEnvironment + { + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ResolveIntegrationTests.cs b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ResolveIntegrationTests.cs new file mode 100644 index 000000000000..ed5604d231a8 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/ResolveIntegrationTests.cs @@ -0,0 +1,288 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using FluentAssertions; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Azure.Iot.ModelsRepository.Tests +{ + public class ResolveIntegrationTests : ModelsRepositoryRecordedTestBase + { + public ResolveIntegrationTests(bool isAsync) : base(isAsync) + { + } + + [TestCase(ModelsRepositoryTestBase.ClientType.Local)] + [TestCase(ModelsRepositoryTestBase.ClientType.Remote)] + public void ResolveWithWrongCasingThrowsException(ModelsRepositoryTestBase.ClientType clientType) + { + const string dtmi = "dtmi:com:example:thermostat;1"; + + ModelsRepositoryClient client = GetClient(clientType); + string expectedExMsg = + string.Format(StandardStrings.GenericResolverError, "dtmi:com:example:thermostat;1") + + " " + + string.Format(StandardStrings.IncorrectDtmiCasing, "dtmi:com:example:thermostat;1", "dtmi:com:example:Thermostat;1"); + + Func act = async () => await client.ResolveAsync(dtmi); + act.Should().Throw().WithMessage(expectedExMsg); + } + + [TestCase("dtmi:com:example:Thermostat:1")] + [TestCase("dtmi:com:example::Thermostat;1")] + [TestCase("com:example:Thermostat;1")] + public void ResolveInvalidDtmiFormatThrowsException(string dtmi) + { + ModelsRepositoryClient client = GetClient(ModelsRepositoryTestBase.ClientType.Local); + string expectedExMsg = $"{string.Format(StandardStrings.GenericResolverError, dtmi)} {string.Format(StandardStrings.InvalidDtmiFormat, dtmi)}"; + + Func act = async () => await client.ResolveAsync(dtmi); + act.Should().Throw().WithMessage(expectedExMsg); + } + + [TestCase(ModelsRepositoryTestBase.ClientType.Local)] + [TestCase(ModelsRepositoryTestBase.ClientType.Remote)] + public void ResolveNoneExistentDtmiFileThrowsException(ModelsRepositoryTestBase.ClientType clientType) + { + const string dtmi = "dtmi:com:example:thermojax;999"; + + ModelsRepositoryClient client = GetClient(clientType); + + Func act = async () => await client.ResolveAsync(dtmi); + act.Should().Throw(); + } + + public void ResolveInvalidDtmiDepsThrowsException() + { + const string dtmi = "dtmi:com:example:invalidmodel;1"; + const string invalidDep = "dtmi:azure:fakeDeviceManagement:FakeDeviceInformation;2"; + + ModelsRepositoryClient client = GetClient(ModelsRepositoryTestBase.ClientType.Local); + Func act = async () => await client.ResolveAsync(dtmi); + act.Should().Throw().WithMessage($"Unable to resolve \"{invalidDep}\""); + } + + [TestCase(ModelsRepositoryTestBase.ClientType.Local)] + [TestCase(ModelsRepositoryTestBase.ClientType.Remote)] + public async Task ResolveSingleModelNoDeps(ModelsRepositoryTestBase.ClientType clientType) + { + const string dtmi = "dtmi:com:example:Thermostat;1"; + + ModelsRepositoryClient client = GetClient(clientType); + IDictionary result = await client.ResolveAsync(dtmi); + result.Keys.Count.Should().Be(1); + result.Should().ContainKey(dtmi); + ModelsRepositoryTestBase.ParseRootDtmiFromJson(result[dtmi]).Should().Be(dtmi); + } + + [TestCase(ModelsRepositoryTestBase.ClientType.Local)] + [TestCase(ModelsRepositoryTestBase.ClientType.Remote)] + public async Task ResolveMultipleModelsNoDeps(ModelsRepositoryTestBase.ClientType clientType) + { + const string dtmi1 = "dtmi:com:example:Thermostat;1"; + const string dtmi2 = "dtmi:azure:DeviceManagement:DeviceInformation;1"; + + ModelsRepositoryClient client = GetClient(clientType); + IDictionary result = await client.ResolveAsync(new string[] { dtmi1, dtmi2 }); + result.Keys.Count.Should().Be(2); + result.Should().ContainKey(dtmi1); + result.Should().ContainKey(dtmi2); + ModelsRepositoryTestBase.ParseRootDtmiFromJson(result[dtmi1]).Should().Be(dtmi1); + ModelsRepositoryTestBase.ParseRootDtmiFromJson(result[dtmi2]).Should().Be(dtmi2); + } + + [TestCase(ModelsRepositoryTestBase.ClientType.Local)] + [TestCase(ModelsRepositoryTestBase.ClientType.Remote)] + public async Task ResolveSingleModelWithDeps(ModelsRepositoryTestBase.ClientType clientType) + { + const string dtmi = "dtmi:com:example:TemperatureController;1"; + const string expectedDeps = "dtmi:com:example:Thermostat;1,dtmi:azure:DeviceManagement:DeviceInformation;1"; + + ModelsRepositoryClient client = GetClient(clientType); + IDictionary result = await client.ResolveAsync(dtmi); + var expectedDtmis = $"{dtmi},{expectedDeps}".Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + + result.Keys.Count.Should().Be(expectedDtmis.Length); + + foreach (var id in expectedDtmis) + { + result.Should().ContainKey(id); + ModelsRepositoryTestBase.ParseRootDtmiFromJson(result[id]).Should().Be(id); + } + } + + public async Task ResolveMultipleModelsWithDeps() + { + const string dtmi1 = "dtmi:com:example:Phone;2"; + const string dtmi2 = "dtmi:com:example:TemperatureController;1"; + const string expectedDeps = "dtmi:com:example:Thermostat;1," + + "dtmi:azure:DeviceManagement:DeviceInformation;1," + + "dtmi:azure:DeviceManagement:DeviceInformation;2," + + "dtmi:com:example:Camera;3"; + + ModelsRepositoryClient client = GetClient(ModelsRepositoryTestBase.ClientType.Local); + IDictionary result = await client.ResolveAsync(new[] { dtmi1, dtmi2 }); + var expectedDtmis = $"{dtmi1},{dtmi2},{expectedDeps}".Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + result.Keys.Count.Should().Be(expectedDtmis.Length); + + foreach (var id in expectedDtmis) + { + result.Should().ContainKey(id); + ModelsRepositoryTestBase.ParseRootDtmiFromJson(result[id]).Should().Be(id); + } + } + + public async Task ResolveMultipleModelsWithDepsFromExtends() + { + const string dtmi1 = "dtmi:com:example:TemperatureController;1"; + const string dtmi2 = "dtmi:com:example:ConferenceRoom;1"; + const string expectedDeps = "dtmi:com:example:Thermostat;1,dtmi:azure:DeviceManagement:DeviceInformation;1,dtmi:com:example:Room;1"; + ModelsRepositoryClient client = GetClient(ModelsRepositoryTestBase.ClientType.Local); + IDictionary result = await client.ResolveAsync(new[] { dtmi1, dtmi2 }); + var expectedDtmis = $"{dtmi1},{dtmi2},{expectedDeps}".Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + + result.Keys.Count.Should().Be(expectedDtmis.Length); + + foreach (var id in expectedDtmis) + { + result.Should().ContainKey(id); + ModelsRepositoryTestBase.ParseRootDtmiFromJson(result[id]).Should().Be(id); + } + } + + public async Task ResolveMultipleModelsWithDepsFromExtendsVariant() + { + const string dtmi1 = "dtmi:com:example:TemperatureController;1"; + const string dtmi2 = "dtmi:com:example:ColdStorage;1"; + const string expectedDeps = "dtmi:com:example:Thermostat;1," + + "dtmi:azure:DeviceManagement:DeviceInformation;1," + + "dtmi:com:example:Room;1," + + "dtmi:com:example:Freezer;1"; + + ModelsRepositoryClient client = GetClient(ModelsRepositoryTestBase.ClientType.Local); + IDictionary result = await client.ResolveAsync(new[] { dtmi1, dtmi2 }); + var expectedDtmis = $"{dtmi1},{dtmi2},{expectedDeps}".Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + + result.Keys.Count.Should().Be(expectedDtmis.Length); + + foreach (var id in expectedDtmis) + { + result.Should().ContainKey(id); + ModelsRepositoryTestBase.ParseRootDtmiFromJson(result[id]).Should().Be(id); + } + } + + public async Task ResolveSingleModelWithDepsFromExtendsInline() + { + const string dtmi = "dtmi:com:example:base;1"; + ModelsRepositoryClient client = GetClient(ModelsRepositoryTestBase.ClientType.Local); + IDictionary result = await client.ResolveAsync(dtmi); + + result.Keys.Count.Should().Be(1); + result.Should().ContainKey(dtmi); + ModelsRepositoryTestBase.ParseRootDtmiFromJson(result[dtmi]).Should().Be(dtmi); + } + + public async Task ResolveSingleModelWithDepsFromExtendsInlineVariant() + { + const string dtmi = "dtmi:com:example:base;2"; + const string expected = "dtmi:com:example:Freezer;1," + + "dtmi:com:example:Thermostat;1"; + + ModelsRepositoryClient client = GetClient(ModelsRepositoryTestBase.ClientType.Local); + IDictionary result = await client.ResolveAsync(dtmi); + var expectedDtmis = $"{dtmi},{expected}".Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + + result.Keys.Count.Should().Be(expectedDtmis.Length); + + foreach (var id in expectedDtmis) + { + result.Should().ContainKey(id); + ModelsRepositoryTestBase.ParseRootDtmiFromJson(result[id]).Should().Be(id); + } + } + + public async Task ResolveEnsuresNoDupes() + { + const string dtmiDupe1 = "dtmi:azure:DeviceManagement:DeviceInformation;1"; + const string dtmiDupe2 = "dtmi:azure:DeviceManagement:DeviceInformation;1"; + + ModelsRepositoryClient client = GetClient(ModelsRepositoryTestBase.ClientType.Local); + IDictionary result = await client.ResolveAsync(new[] { dtmiDupe1, dtmiDupe2 }); + + result.Keys.Count.Should().Be(1); + ModelsRepositoryTestBase.ParseRootDtmiFromJson(result[dtmiDupe1]).Should().Be(dtmiDupe1); + } + + [TestCase(ModelsRepositoryTestBase.ClientType.Local)] + [TestCase(ModelsRepositoryTestBase.ClientType.Remote)] + public async Task ResolveSingleModelWithDepsDisableDependencyResolution(ModelsRepositoryTestBase.ClientType clientType) + { + const string dtmi = "dtmi:com:example:Thermostat;1"; + + ModelsRepositoryClientOptions options = new ModelsRepositoryClientOptions(resolutionOption: DependencyResolutionOption.Disabled); + ModelsRepositoryClient client = GetClient(clientType, options); + + IDictionary result = await client.ResolveAsync(dtmi); + + result.Keys.Count.Should().Be(1); + result.Should().ContainKey(dtmi); + ModelsRepositoryTestBase.ParseRootDtmiFromJson(result[dtmi]).Should().Be(dtmi); + } + + [TestCase(ModelsRepositoryTestBase.ClientType.Local)] + [TestCase(ModelsRepositoryTestBase.ClientType.Remote)] + public async Task ResolveSingleModelTryFromExpanded(ModelsRepositoryTestBase.ClientType clientType) + { + const string dtmi = "dtmi:com:example:TemperatureController;1"; + const string expectedDeps = "dtmi:com:example:Thermostat;1,dtmi:azure:DeviceManagement:DeviceInformation;1"; + + var expectedDtmis = $"{dtmi},{expectedDeps}".Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + + ModelsRepositoryClientOptions options = new ModelsRepositoryClientOptions(resolutionOption: DependencyResolutionOption.TryFromExpanded); + ModelsRepositoryClient client = GetClient(clientType, options); + + IDictionary result = await client.ResolveAsync(dtmi); + + result.Keys.Count.Should().Be(expectedDtmis.Length); + + foreach (var id in expectedDtmis) + { + result.Should().ContainKey(id); + ModelsRepositoryTestBase.ParseRootDtmiFromJson(result[id]).Should().Be(id); + } + } + + public async Task ResolveMultipleModelsTryFromExpandedPartial() + { + const string dtmisExpanded = "dtmi:com:example:TemperatureController;1," + // Expanded available. + "dtmi:com:example:Thermostat;1," + + "dtmi:azure:DeviceManagement:DeviceInformation;1"; + + const string dtmisNonExpanded = "dtmi:com:example:ColdStorage;1," + // Model uses extends[], No Expanded available. + "dtmi:com:example:Room;1," + + "dtmi:com:example:Freezer;1"; + + string[] expandedDtmis = dtmisExpanded.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + string[] nonExpandedDtmis = dtmisNonExpanded.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + string[] totalDtmis = expandedDtmis.Concat(nonExpandedDtmis).ToArray(); + + ModelsRepositoryClientOptions options = new ModelsRepositoryClientOptions(resolutionOption: DependencyResolutionOption.TryFromExpanded); + ModelsRepositoryClient client = GetClient(ModelsRepositoryTestBase.ClientType.Local, options); + + // Multi-resolve dtmi:com:example:TemperatureController;1 + dtmi:com:example:ColdStorage;1 + IDictionary result = await client.ResolveAsync(new[] { expandedDtmis[0], nonExpandedDtmis[0] }); + + result.Keys.Count.Should().Be(totalDtmis.Length); + foreach (string id in totalDtmis) + { + result.Should().ContainKey(id); + ModelsRepositoryTestBase.ParseRootDtmiFromJson(result[id]).Should().Be(id); + } + } + } +} diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%com%example%Thermostat;1%).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%com%example%Thermostat;1%).json new file mode 100644 index 000000000000..9ec129124d85 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%com%example%Thermostat;1%).json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "1896302144" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%com%example%Thermostat;1%)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%com%example%Thermostat;1%)Async.json new file mode 100644 index 000000000000..4a64a54fcf8e --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%com%example%Thermostat;1%)Async.json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "1739461053" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%%Thermostat;1%).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%%Thermostat;1%).json new file mode 100644 index 000000000000..508d82a82bed --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%%Thermostat;1%).json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "1125774216" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%%Thermostat;1%)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%%Thermostat;1%)Async.json new file mode 100644 index 000000000000..9664f89d7d9e --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%%Thermostat;1%)Async.json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "968933125" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%Thermostat%1%).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%Thermostat%1%).json new file mode 100644 index 000000000000..aec97abbf3fb --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%Thermostat%1%).json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "1971177223" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%Thermostat%1%)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%Thermostat%1%)Async.json new file mode 100644 index 000000000000..ceaf57de875c --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveInvalidDtmiFormatThrowsException(%dtmi%com%example%Thermostat%1%)Async.json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "1320305016" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Local).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Local).json new file mode 100644 index 000000000000..9ec129124d85 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Local).json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "1896302144" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Local)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Local)Async.json new file mode 100644 index 000000000000..c9e30f9489ae --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Local)Async.json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "781661371" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Remote).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Remote).json new file mode 100644 index 000000000000..ed0c511d5896 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Remote).json @@ -0,0 +1,236 @@ +{ + "Entries": [ + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/thermostat-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-6710aa072309dd4da9eae2b20a6a583d-a0d64c4329a4a74d-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "f15f2d9464e9ec2f0fcb34cdb2ce898a", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "2469", + "Content-MD5": "U0VZgOgpfb6bwvG5UDVZuw==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEABC53BA\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:57 GMT", + "Server": [ + "ECAcc", + "(sed/E15D)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "7c45bf94-401e-006f-228a-05fc4e000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat\u0022,\n", + " \u0022description\u0022: \u0022Reports current temperature and provides desired temperature control.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022temperature\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature\u0022,\n", + " \u0022description\u0022: \u0022Temperature in degrees Celsius.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022targetTemperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022displayName\u0022: \u0022Target Temperature\u0022,\n", + " \u0022description\u0022: \u0022Allows to remotely specify the desired target temperature.\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022writable\u0022: true\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022maxTempSinceLastReboot\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature since last reboot.\u0022,\n", + " \u0022description\u0022: \u0022Returns the max temperature since last device reboot.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022getMaxMinReport\u0022,\n", + " \u0022displayName\u0022: \u0022Get Max-Min report.\u0022,\n", + " \u0022description\u0022: \u0022This command returns the max, min and average temperature from the specified time to the current time.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022since\u0022,\n", + " \u0022displayName\u0022: \u0022Since\u0022,\n", + " \u0022description\u0022: \u0022Period to return the max-min report.\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " \u0022response\u0022: {\n", + " \u0022name\u0022: \u0022tempReport\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Report\u0022,\n", + " \u0022schema\u0022: {\n", + " \u0022@type\u0022: \u0022Object\u0022,\n", + " \u0022fields\u0022: [\n", + " {\n", + " \u0022name\u0022: \u0022maxTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022minTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Min temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022avgTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Average Temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022startTime\u0022,\n", + " \u0022displayName\u0022: \u0022Start Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022endTime\u0022,\n", + " \u0022displayName\u0022: \u0022End Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " }\n", + " ]\n", + " }\n", + " }\n", + " }\n", + " ]\n", + "}" + ] + }, + { + "RequestUri": "https://devicemodels.azure.com/dtmi/azure/devicemanagement/deviceinformation-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-6710aa072309dd4da9eae2b20a6a583d-5a0f98d0206e2742-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "cbcd0f251b4278013cc839ccf7d2faa7", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "2212", + "Content-MD5": "oCzHP9acH\u002B/\u002BFQOv2V56NA==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEBC614F0\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:59 GMT", + "Server": [ + "ECAcc", + "(sed/E110)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "3933dec4-901e-0002-018a-056177000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:azure:DeviceManagement:DeviceInformation;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Device Information\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022manufacturer\u0022,\n", + " \u0022displayName\u0022: \u0022Manufacturer\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Company name of the device manufacturer. This could be the same as the name of the original equipment manufacturer (OEM). Ex. Contoso.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022model\u0022,\n", + " \u0022displayName\u0022: \u0022Device model\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Device model name or ID. Ex. Surface Book 2.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022swVersion\u0022,\n", + " \u0022displayName\u0022: \u0022Software version\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Version of the software on your device. This could be the version of your firmware. Ex. 1.3.45\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022osName\u0022,\n", + " \u0022displayName\u0022: \u0022Operating system name\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Name of the operating system on the device. Ex. Windows 10 IoT Core.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022processorArchitecture\u0022,\n", + " \u0022displayName\u0022: \u0022Processor architecture\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Architecture of the processor on the device. Ex. x64 or ARM.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022processorManufacturer\u0022,\n", + " \u0022displayName\u0022: \u0022Processor manufacturer\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Name of the manufacturer of the processor on the device. Ex. Intel.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022totalStorage\u0022,\n", + " \u0022displayName\u0022: \u0022Total storage\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022description\u0022: \u0022Total available storage on the device in kilobytes. Ex. 2048000 kilobytes.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022totalMemory\u0022,\n", + " \u0022displayName\u0022: \u0022Total memory\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022description\u0022: \u0022Total available memory on the device in kilobytes. Ex. 256000 kilobytes.\u0022\n", + " }\n", + " ]\n", + " }" + ] + } + ], + "Variables": { + "RandomSeed": "519346425" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Remote)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Remote)Async.json new file mode 100644 index 000000000000..0480026e03e8 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveMultipleModelsNoDeps(Remote)Async.json @@ -0,0 +1,236 @@ +{ + "Entries": [ + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/thermostat-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-a83d4508bf86464ba54548bd79e9bff7-3e4961a430dc2d4d-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "b259b4dec85c5c4fbd98209dd4d9fd93", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "2469", + "Content-MD5": "U0VZgOgpfb6bwvG5UDVZuw==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEABC53BA\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:57 GMT", + "Server": [ + "ECAcc", + "(sed/E15D)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "7c45bf94-401e-006f-228a-05fc4e000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat\u0022,\n", + " \u0022description\u0022: \u0022Reports current temperature and provides desired temperature control.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022temperature\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature\u0022,\n", + " \u0022description\u0022: \u0022Temperature in degrees Celsius.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022targetTemperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022displayName\u0022: \u0022Target Temperature\u0022,\n", + " \u0022description\u0022: \u0022Allows to remotely specify the desired target temperature.\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022writable\u0022: true\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022maxTempSinceLastReboot\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature since last reboot.\u0022,\n", + " \u0022description\u0022: \u0022Returns the max temperature since last device reboot.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022getMaxMinReport\u0022,\n", + " \u0022displayName\u0022: \u0022Get Max-Min report.\u0022,\n", + " \u0022description\u0022: \u0022This command returns the max, min and average temperature from the specified time to the current time.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022since\u0022,\n", + " \u0022displayName\u0022: \u0022Since\u0022,\n", + " \u0022description\u0022: \u0022Period to return the max-min report.\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " \u0022response\u0022: {\n", + " \u0022name\u0022: \u0022tempReport\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Report\u0022,\n", + " \u0022schema\u0022: {\n", + " \u0022@type\u0022: \u0022Object\u0022,\n", + " \u0022fields\u0022: [\n", + " {\n", + " \u0022name\u0022: \u0022maxTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022minTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Min temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022avgTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Average Temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022startTime\u0022,\n", + " \u0022displayName\u0022: \u0022Start Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022endTime\u0022,\n", + " \u0022displayName\u0022: \u0022End Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " }\n", + " ]\n", + " }\n", + " }\n", + " }\n", + " ]\n", + "}" + ] + }, + { + "RequestUri": "https://devicemodels.azure.com/dtmi/azure/devicemanagement/deviceinformation-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-a83d4508bf86464ba54548bd79e9bff7-b3d8dd462c18fd46-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "7613fd841a1f4524a36ae4b8b12b2596", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "2212", + "Content-MD5": "oCzHP9acH\u002B/\u002BFQOv2V56NA==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEBC614F0\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:59 GMT", + "Server": [ + "ECAcc", + "(sed/E110)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "3933dec4-901e-0002-018a-056177000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:azure:DeviceManagement:DeviceInformation;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Device Information\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022manufacturer\u0022,\n", + " \u0022displayName\u0022: \u0022Manufacturer\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Company name of the device manufacturer. This could be the same as the name of the original equipment manufacturer (OEM). Ex. Contoso.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022model\u0022,\n", + " \u0022displayName\u0022: \u0022Device model\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Device model name or ID. Ex. Surface Book 2.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022swVersion\u0022,\n", + " \u0022displayName\u0022: \u0022Software version\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Version of the software on your device. This could be the version of your firmware. Ex. 1.3.45\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022osName\u0022,\n", + " \u0022displayName\u0022: \u0022Operating system name\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Name of the operating system on the device. Ex. Windows 10 IoT Core.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022processorArchitecture\u0022,\n", + " \u0022displayName\u0022: \u0022Processor architecture\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Architecture of the processor on the device. Ex. x64 or ARM.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022processorManufacturer\u0022,\n", + " \u0022displayName\u0022: \u0022Processor manufacturer\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Name of the manufacturer of the processor on the device. Ex. Intel.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022totalStorage\u0022,\n", + " \u0022displayName\u0022: \u0022Total storage\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022description\u0022: \u0022Total available storage on the device in kilobytes. Ex. 2048000 kilobytes.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022totalMemory\u0022,\n", + " \u0022displayName\u0022: \u0022Total memory\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022description\u0022: \u0022Total available memory on the device in kilobytes. Ex. 256000 kilobytes.\u0022\n", + " }\n", + " ]\n", + " }" + ] + } + ], + "Variables": { + "RandomSeed": "1200817408" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Local).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Local).json new file mode 100644 index 000000000000..127240e783d9 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Local).json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "1582451849" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Local)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Local)Async.json new file mode 100644 index 000000000000..87dc3c91fbd5 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Local)Async.json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "662173763" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Remote).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Remote).json new file mode 100644 index 000000000000..b84a599c8135 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Remote).json @@ -0,0 +1,38 @@ +{ + "Entries": [ + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/thermojax-999.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-36d291fc90a4334dac3eb38f6466b8e8-91b4698d8e996846-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "15a3190d902fe96003d29f3acfcbafb3", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 404, + "ResponseHeaders": { + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Content-Length": "321", + "Content-Type": "text/html", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "Server": [ + "Windows-Azure-Web/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-error-code": "WebContentNotFound", + "x-ms-request-id": "872d6906-f01e-0014-47f4-062b59000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": "\u003C!DOCTYPE html\u003E\u003Chtml\u003E\u003Chead\u003E\u003Ctitle\u003EWebContentNotFound\u003C/title\u003E\u003C/head\u003E\u003Cbody\u003E\u003Ch1\u003EThe requested content does not exist.\u003C/h1\u003E\u003Cp\u003E\u003Cul\u003E\u003Cli\u003EHttpStatusCode: 404\u003C/li\u003E\u003Cli\u003EErrorCode: WebContentNotFound\u003C/li\u003E\u003Cli\u003ERequestId : 872d6906-f01e-0014-47f4-062b59000000\u003C/li\u003E\u003Cli\u003ETimeStamp : 2021-02-19T19:24:50.4869209Z\u003C/li\u003E\u003C/ul\u003E\u003C/p\u003E\u003C/body\u003E\u003C/html\u003E" + } + ], + "Variables": { + "RandomSeed": "205496130" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Remote)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Remote)Async.json new file mode 100644 index 000000000000..0e45232cc418 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveNoneExistentDtmiFileThrowsException(Remote)Async.json @@ -0,0 +1,38 @@ +{ + "Entries": [ + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/thermojax-999.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-ade836eb9ce7d2428a323c9d4c253a6f-02b14cddecdc854f-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "800de3d2ffcd1d2e908429ff4f76246c", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 404, + "ResponseHeaders": { + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Content-Length": "321", + "Content-Type": "text/html", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "Server": [ + "Windows-Azure-Web/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "x-ms-error-code": "WebContentNotFound", + "x-ms-request-id": "2908bd3c-001e-0003-17f4-064a75000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": "\u003C!DOCTYPE html\u003E\u003Chtml\u003E\u003Chead\u003E\u003Ctitle\u003EWebContentNotFound\u003C/title\u003E\u003C/head\u003E\u003Cbody\u003E\u003Ch1\u003EThe requested content does not exist.\u003C/h1\u003E\u003Cp\u003E\u003Cul\u003E\u003Cli\u003EHttpStatusCode: 404\u003C/li\u003E\u003Cli\u003EErrorCode: WebContentNotFound\u003C/li\u003E\u003Cli\u003ERequestId : 2908bd3c-001e-0003-17f4-064a75000000\u003C/li\u003E\u003Cli\u003ETimeStamp : 2021-02-19T19:24:50.7224903Z\u003C/li\u003E\u003C/ul\u003E\u003C/p\u003E\u003C/body\u003E\u003C/html\u003E" + } + ], + "Variables": { + "RandomSeed": "1432701691" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Local).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Local).json new file mode 100644 index 000000000000..23ba18a88c34 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Local).json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "1814336132" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Local)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Local)Async.json new file mode 100644 index 000000000000..c3c989e1dc4b --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Local)Async.json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "474902009" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Remote).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Remote).json new file mode 100644 index 000000000000..d9c5962f2a2b --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Remote).json @@ -0,0 +1,134 @@ +{ + "Entries": [ + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/thermostat-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-26ed0dcd5e88e34db4cf594009e3aac9-da7273e30058d64b-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "c0c1d85a27f38f70096f19702012afc3", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "2469", + "Content-MD5": "U0VZgOgpfb6bwvG5UDVZuw==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEABC53BA\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:57 GMT", + "Server": [ + "ECAcc", + "(sed/E15D)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "7c45bf94-401e-006f-228a-05fc4e000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat\u0022,\n", + " \u0022description\u0022: \u0022Reports current temperature and provides desired temperature control.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022temperature\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature\u0022,\n", + " \u0022description\u0022: \u0022Temperature in degrees Celsius.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022targetTemperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022displayName\u0022: \u0022Target Temperature\u0022,\n", + " \u0022description\u0022: \u0022Allows to remotely specify the desired target temperature.\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022writable\u0022: true\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022maxTempSinceLastReboot\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature since last reboot.\u0022,\n", + " \u0022description\u0022: \u0022Returns the max temperature since last device reboot.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022getMaxMinReport\u0022,\n", + " \u0022displayName\u0022: \u0022Get Max-Min report.\u0022,\n", + " \u0022description\u0022: \u0022This command returns the max, min and average temperature from the specified time to the current time.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022since\u0022,\n", + " \u0022displayName\u0022: \u0022Since\u0022,\n", + " \u0022description\u0022: \u0022Period to return the max-min report.\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " \u0022response\u0022: {\n", + " \u0022name\u0022: \u0022tempReport\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Report\u0022,\n", + " \u0022schema\u0022: {\n", + " \u0022@type\u0022: \u0022Object\u0022,\n", + " \u0022fields\u0022: [\n", + " {\n", + " \u0022name\u0022: \u0022maxTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022minTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Min temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022avgTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Average Temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022startTime\u0022,\n", + " \u0022displayName\u0022: \u0022Start Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022endTime\u0022,\n", + " \u0022displayName\u0022: \u0022End Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " }\n", + " ]\n", + " }\n", + " }\n", + " }\n", + " ]\n", + "}" + ] + } + ], + "Variables": { + "RandomSeed": "1814336132" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Remote)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Remote)Async.json new file mode 100644 index 000000000000..2728d50d5e07 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelNoDeps(Remote)Async.json @@ -0,0 +1,134 @@ +{ + "Entries": [ + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/thermostat-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-2aa19654ba77924693bf45888062474f-8ebc5bffbe11ba45-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "07953379f6e5a76fcad114097666fdb4", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "2469", + "Content-MD5": "U0VZgOgpfb6bwvG5UDVZuw==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEABC53BA\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:57 GMT", + "Server": [ + "ECAcc", + "(sed/E15D)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "7c45bf94-401e-006f-228a-05fc4e000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat\u0022,\n", + " \u0022description\u0022: \u0022Reports current temperature and provides desired temperature control.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022temperature\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature\u0022,\n", + " \u0022description\u0022: \u0022Temperature in degrees Celsius.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022targetTemperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022displayName\u0022: \u0022Target Temperature\u0022,\n", + " \u0022description\u0022: \u0022Allows to remotely specify the desired target temperature.\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022writable\u0022: true\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022maxTempSinceLastReboot\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature since last reboot.\u0022,\n", + " \u0022description\u0022: \u0022Returns the max temperature since last device reboot.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022getMaxMinReport\u0022,\n", + " \u0022displayName\u0022: \u0022Get Max-Min report.\u0022,\n", + " \u0022description\u0022: \u0022This command returns the max, min and average temperature from the specified time to the current time.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022since\u0022,\n", + " \u0022displayName\u0022: \u0022Since\u0022,\n", + " \u0022description\u0022: \u0022Period to return the max-min report.\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " \u0022response\u0022: {\n", + " \u0022name\u0022: \u0022tempReport\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Report\u0022,\n", + " \u0022schema\u0022: {\n", + " \u0022@type\u0022: \u0022Object\u0022,\n", + " \u0022fields\u0022: [\n", + " {\n", + " \u0022name\u0022: \u0022maxTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022minTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Min temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022avgTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Average Temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022startTime\u0022,\n", + " \u0022displayName\u0022: \u0022Start Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022endTime\u0022,\n", + " \u0022displayName\u0022: \u0022End Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " }\n", + " ]\n", + " }\n", + " }\n", + " }\n", + " ]\n", + "}" + ] + } + ], + "Variables": { + "RandomSeed": "123530118" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Local).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Local).json new file mode 100644 index 000000000000..000596ad0fab --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Local).json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "86008522" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Local)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Local)Async.json new file mode 100644 index 000000000000..65f968614634 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Local)Async.json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "894058046" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Remote).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Remote).json new file mode 100644 index 000000000000..3fe1fccb9565 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Remote).json @@ -0,0 +1,260 @@ +{ + "Entries": [ + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/temperaturecontroller-1.expanded.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-3560143abbe8ee499c3680f3ba22c5e7-3765e2f1fea32243-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "27186f54422b6fe0f3619ea05960422f", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "171032", + "Content-Length": "6751", + "Content-MD5": "BnZpV2\u002B7Z2t0sxAxOhmBdw==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEAC10FCC\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:57 GMT", + "Server": [ + "ECAcc", + "(sed/E181)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "cf7eff99-301e-0018-2a66-05df40000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "[\n", + " {\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:TemperatureController;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Controller\u0022,\n", + " \u0022description\u0022: \u0022Device with two thermostats and remote reboot.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022DataSize\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022workingSet\u0022,\n", + " \u0022displayName\u0022: \u0022Working Set\u0022,\n", + " \u0022description\u0022: \u0022Current working set of the device memory in KiB.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022kibibyte\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022serialNumber\u0022,\n", + " \u0022displayName\u0022: \u0022Serial Number\u0022,\n", + " \u0022description\u0022: \u0022Serial number of the device.\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022reboot\u0022,\n", + " \u0022displayName\u0022: \u0022Reboot\u0022,\n", + " \u0022description\u0022: \u0022Reboots the device after waiting the number of seconds specified.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022delay\u0022,\n", + " \u0022displayName\u0022: \u0022Delay\u0022,\n", + " \u0022description\u0022: \u0022Number of seconds to wait before rebooting the device.\u0022,\n", + " \u0022schema\u0022: \u0022integer\u0022\n", + " }\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Component\u0022,\n", + " \u0022schema\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022name\u0022: \u0022thermostat1\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat One\u0022,\n", + " \u0022description\u0022: \u0022Thermostat One of Two.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Component\u0022,\n", + " \u0022schema\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022name\u0022: \u0022thermostat2\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat Two\u0022,\n", + " \u0022description\u0022: \u0022Thermostat Two of Two.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Component\u0022,\n", + " \u0022schema\u0022: \u0022dtmi:azure:DeviceManagement:DeviceInformation;1\u0022,\n", + " \u0022name\u0022: \u0022deviceInformation\u0022,\n", + " \u0022displayName\u0022: \u0022Device Information interface\u0022,\n", + " \u0022description\u0022: \u0022Optional interface with basic device hardware information.\u0022\n", + " }\n", + " ]\n", + " },\n", + " {\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat\u0022,\n", + " \u0022description\u0022: \u0022Reports current temperature and provides desired temperature control.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022temperature\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature\u0022,\n", + " \u0022description\u0022: \u0022Temperature in degrees Celsius.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022targetTemperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022displayName\u0022: \u0022Target Temperature\u0022,\n", + " \u0022description\u0022: \u0022Allows to remotely specify the desired target temperature.\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022writable\u0022: true\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022maxTempSinceLastReboot\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature since last reboot.\u0022,\n", + " \u0022description\u0022: \u0022Returns the max temperature since last device reboot.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022getMaxMinReport\u0022,\n", + " \u0022displayName\u0022: \u0022Get Max-Min report.\u0022,\n", + " \u0022description\u0022: \u0022This command returns the max, min and average temperature from the specified time to the current time.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022since\u0022,\n", + " \u0022displayName\u0022: \u0022Since\u0022,\n", + " \u0022description\u0022: \u0022Period to return the max-min report.\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " \u0022response\u0022: {\n", + " \u0022name\u0022: \u0022tempReport\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Report\u0022,\n", + " \u0022schema\u0022: {\n", + " \u0022@type\u0022: \u0022Object\u0022,\n", + " \u0022fields\u0022: [\n", + " {\n", + " \u0022name\u0022: \u0022maxTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022minTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Min temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022avgTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Average Temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022startTime\u0022,\n", + " \u0022displayName\u0022: \u0022Start Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022endTime\u0022,\n", + " \u0022displayName\u0022: \u0022End Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " }\n", + " ]\n", + " }\n", + " }\n", + " }\n", + " ]\n", + " },\n", + " {\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:azure:DeviceManagement:DeviceInformation;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Device Information\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022manufacturer\u0022,\n", + " \u0022displayName\u0022: \u0022Manufacturer\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Company name of the device manufacturer. This could be the same as the name of the original equipment manufacturer (OEM). Ex. Contoso.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022model\u0022,\n", + " \u0022displayName\u0022: \u0022Device model\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Device model name or ID. Ex. Surface Book 2.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022swVersion\u0022,\n", + " \u0022displayName\u0022: \u0022Software version\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Version of the software on your device. This could be the version of your firmware. Ex. 1.3.45\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022osName\u0022,\n", + " \u0022displayName\u0022: \u0022Operating system name\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Name of the operating system on the device. Ex. Windows 10 IoT Core.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022processorArchitecture\u0022,\n", + " \u0022displayName\u0022: \u0022Processor architecture\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Architecture of the processor on the device. Ex. x64 or ARM.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022processorManufacturer\u0022,\n", + " \u0022displayName\u0022: \u0022Processor manufacturer\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Name of the manufacturer of the processor on the device. Ex. Intel.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022totalStorage\u0022,\n", + " \u0022displayName\u0022: \u0022Total storage\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022description\u0022: \u0022Total available storage on the device in kilobytes. Ex. 2048000 kilobytes.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022totalMemory\u0022,\n", + " \u0022displayName\u0022: \u0022Total memory\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022description\u0022: \u0022Total available memory on the device in kilobytes. Ex. 256000 kilobytes.\u0022\n", + " }\n", + " ]\n", + " }\n", + "]" + ] + } + ], + "Variables": { + "RandomSeed": "856536450" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Remote)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Remote)Async.json new file mode 100644 index 000000000000..567acd19b794 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelTryFromExpanded(Remote)Async.json @@ -0,0 +1,260 @@ +{ + "Entries": [ + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/temperaturecontroller-1.expanded.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-15e5a468265f5b449fea789844d1ad18-dff3192ab634ea42-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "4b5c5bcc71726d10e772090d825e6957", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "171032", + "Content-Length": "6751", + "Content-MD5": "BnZpV2\u002B7Z2t0sxAxOhmBdw==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEAC10FCC\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:57 GMT", + "Server": [ + "ECAcc", + "(sed/E181)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "cf7eff99-301e-0018-2a66-05df40000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "[\n", + " {\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:TemperatureController;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Controller\u0022,\n", + " \u0022description\u0022: \u0022Device with two thermostats and remote reboot.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022DataSize\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022workingSet\u0022,\n", + " \u0022displayName\u0022: \u0022Working Set\u0022,\n", + " \u0022description\u0022: \u0022Current working set of the device memory in KiB.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022kibibyte\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022serialNumber\u0022,\n", + " \u0022displayName\u0022: \u0022Serial Number\u0022,\n", + " \u0022description\u0022: \u0022Serial number of the device.\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022reboot\u0022,\n", + " \u0022displayName\u0022: \u0022Reboot\u0022,\n", + " \u0022description\u0022: \u0022Reboots the device after waiting the number of seconds specified.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022delay\u0022,\n", + " \u0022displayName\u0022: \u0022Delay\u0022,\n", + " \u0022description\u0022: \u0022Number of seconds to wait before rebooting the device.\u0022,\n", + " \u0022schema\u0022: \u0022integer\u0022\n", + " }\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Component\u0022,\n", + " \u0022schema\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022name\u0022: \u0022thermostat1\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat One\u0022,\n", + " \u0022description\u0022: \u0022Thermostat One of Two.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Component\u0022,\n", + " \u0022schema\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022name\u0022: \u0022thermostat2\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat Two\u0022,\n", + " \u0022description\u0022: \u0022Thermostat Two of Two.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Component\u0022,\n", + " \u0022schema\u0022: \u0022dtmi:azure:DeviceManagement:DeviceInformation;1\u0022,\n", + " \u0022name\u0022: \u0022deviceInformation\u0022,\n", + " \u0022displayName\u0022: \u0022Device Information interface\u0022,\n", + " \u0022description\u0022: \u0022Optional interface with basic device hardware information.\u0022\n", + " }\n", + " ]\n", + " },\n", + " {\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat\u0022,\n", + " \u0022description\u0022: \u0022Reports current temperature and provides desired temperature control.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022temperature\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature\u0022,\n", + " \u0022description\u0022: \u0022Temperature in degrees Celsius.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022targetTemperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022displayName\u0022: \u0022Target Temperature\u0022,\n", + " \u0022description\u0022: \u0022Allows to remotely specify the desired target temperature.\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022writable\u0022: true\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022maxTempSinceLastReboot\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature since last reboot.\u0022,\n", + " \u0022description\u0022: \u0022Returns the max temperature since last device reboot.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022getMaxMinReport\u0022,\n", + " \u0022displayName\u0022: \u0022Get Max-Min report.\u0022,\n", + " \u0022description\u0022: \u0022This command returns the max, min and average temperature from the specified time to the current time.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022since\u0022,\n", + " \u0022displayName\u0022: \u0022Since\u0022,\n", + " \u0022description\u0022: \u0022Period to return the max-min report.\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " \u0022response\u0022: {\n", + " \u0022name\u0022: \u0022tempReport\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Report\u0022,\n", + " \u0022schema\u0022: {\n", + " \u0022@type\u0022: \u0022Object\u0022,\n", + " \u0022fields\u0022: [\n", + " {\n", + " \u0022name\u0022: \u0022maxTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022minTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Min temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022avgTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Average Temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022startTime\u0022,\n", + " \u0022displayName\u0022: \u0022Start Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022endTime\u0022,\n", + " \u0022displayName\u0022: \u0022End Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " }\n", + " ]\n", + " }\n", + " }\n", + " }\n", + " ]\n", + " },\n", + " {\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:azure:DeviceManagement:DeviceInformation;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Device Information\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022manufacturer\u0022,\n", + " \u0022displayName\u0022: \u0022Manufacturer\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Company name of the device manufacturer. This could be the same as the name of the original equipment manufacturer (OEM). Ex. Contoso.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022model\u0022,\n", + " \u0022displayName\u0022: \u0022Device model\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Device model name or ID. Ex. Surface Book 2.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022swVersion\u0022,\n", + " \u0022displayName\u0022: \u0022Software version\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Version of the software on your device. This could be the version of your firmware. Ex. 1.3.45\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022osName\u0022,\n", + " \u0022displayName\u0022: \u0022Operating system name\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Name of the operating system on the device. Ex. Windows 10 IoT Core.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022processorArchitecture\u0022,\n", + " \u0022displayName\u0022: \u0022Processor architecture\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Architecture of the processor on the device. Ex. x64 or ARM.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022processorManufacturer\u0022,\n", + " \u0022displayName\u0022: \u0022Processor manufacturer\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Name of the manufacturer of the processor on the device. Ex. Intel.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022totalStorage\u0022,\n", + " \u0022displayName\u0022: \u0022Total storage\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022description\u0022: \u0022Total available storage on the device in kilobytes. Ex. 2048000 kilobytes.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022totalMemory\u0022,\n", + " \u0022displayName\u0022: \u0022Total memory\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022description\u0022: \u0022Total available memory on the device in kilobytes. Ex. 256000 kilobytes.\u0022\n", + " }\n", + " ]\n", + " }\n", + "]" + ] + } + ], + "Variables": { + "RandomSeed": "542686155" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Local).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Local).json new file mode 100644 index 000000000000..313b8876cf22 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Local).json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "1275692487" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Local)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Local)Async.json new file mode 100644 index 000000000000..0eb9273a00dc --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Local)Async.json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "1313214083" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Remote).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Remote).json new file mode 100644 index 000000000000..4b5d462e5198 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Remote).json @@ -0,0 +1,334 @@ +{ + "Entries": [ + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/temperaturecontroller-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-4a527f4cab25f940a06f30a678bc25f8-35b93feae4f6a64b-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "8e75074e5f63504fdd5b22d19bafd69b", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "1762", + "Content-MD5": "w8OrFrbCwZwtVo6WaKDWxQ==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEABA3051\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:57 GMT", + "Server": [ + "ECAcc", + "(sed/E17F)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "8ae2e233-d01e-0092-748a-058600000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:TemperatureController;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Controller\u0022,\n", + " \u0022description\u0022: \u0022Device with two thermostats and remote reboot.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022DataSize\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022workingSet\u0022,\n", + " \u0022displayName\u0022: \u0022Working Set\u0022,\n", + " \u0022description\u0022: \u0022Current working set of the device memory in KiB.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022kibibyte\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022serialNumber\u0022,\n", + " \u0022displayName\u0022: \u0022Serial Number\u0022,\n", + " \u0022description\u0022: \u0022Serial number of the device.\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022reboot\u0022,\n", + " \u0022displayName\u0022: \u0022Reboot\u0022,\n", + " \u0022description\u0022: \u0022Reboots the device after waiting the number of seconds specified.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022delay\u0022,\n", + " \u0022displayName\u0022: \u0022Delay\u0022,\n", + " \u0022description\u0022: \u0022Number of seconds to wait before rebooting the device.\u0022,\n", + " \u0022schema\u0022: \u0022integer\u0022\n", + " }\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Component\u0022,\n", + " \u0022schema\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022name\u0022: \u0022thermostat1\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat One\u0022,\n", + " \u0022description\u0022: \u0022Thermostat One of Two.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Component\u0022,\n", + " \u0022schema\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022name\u0022: \u0022thermostat2\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat Two\u0022,\n", + " \u0022description\u0022: \u0022Thermostat Two of Two.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Component\u0022,\n", + " \u0022schema\u0022: \u0022dtmi:azure:DeviceManagement:DeviceInformation;1\u0022,\n", + " \u0022name\u0022: \u0022deviceInformation\u0022,\n", + " \u0022displayName\u0022: \u0022Device Information interface\u0022,\n", + " \u0022description\u0022: \u0022Optional interface with basic device hardware information.\u0022\n", + " }\n", + " ]\n", + "}" + ] + }, + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/thermostat-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-4a527f4cab25f940a06f30a678bc25f8-128dfc0ad06f4d41-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "0b41dd91b50363fc55b81fb0a6726f7b", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "2469", + "Content-MD5": "U0VZgOgpfb6bwvG5UDVZuw==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEABC53BA\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:57 GMT", + "Server": [ + "ECAcc", + "(sed/E15D)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "7c45bf94-401e-006f-228a-05fc4e000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat\u0022,\n", + " \u0022description\u0022: \u0022Reports current temperature and provides desired temperature control.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022temperature\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature\u0022,\n", + " \u0022description\u0022: \u0022Temperature in degrees Celsius.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022targetTemperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022displayName\u0022: \u0022Target Temperature\u0022,\n", + " \u0022description\u0022: \u0022Allows to remotely specify the desired target temperature.\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022writable\u0022: true\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022maxTempSinceLastReboot\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature since last reboot.\u0022,\n", + " \u0022description\u0022: \u0022Returns the max temperature since last device reboot.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022getMaxMinReport\u0022,\n", + " \u0022displayName\u0022: \u0022Get Max-Min report.\u0022,\n", + " \u0022description\u0022: \u0022This command returns the max, min and average temperature from the specified time to the current time.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022since\u0022,\n", + " \u0022displayName\u0022: \u0022Since\u0022,\n", + " \u0022description\u0022: \u0022Period to return the max-min report.\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " \u0022response\u0022: {\n", + " \u0022name\u0022: \u0022tempReport\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Report\u0022,\n", + " \u0022schema\u0022: {\n", + " \u0022@type\u0022: \u0022Object\u0022,\n", + " \u0022fields\u0022: [\n", + " {\n", + " \u0022name\u0022: \u0022maxTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022minTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Min temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022avgTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Average Temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022startTime\u0022,\n", + " \u0022displayName\u0022: \u0022Start Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022endTime\u0022,\n", + " \u0022displayName\u0022: \u0022End Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " }\n", + " ]\n", + " }\n", + " }\n", + " }\n", + " ]\n", + "}" + ] + }, + { + "RequestUri": "https://devicemodels.azure.com/dtmi/azure/devicemanagement/deviceinformation-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-4a527f4cab25f940a06f30a678bc25f8-8da9b3f88c628442-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "b23d60559fd92b1a25e232c0eb863114", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "2212", + "Content-MD5": "oCzHP9acH\u002B/\u002BFQOv2V56NA==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEBC614F0\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:59 GMT", + "Server": [ + "ECAcc", + "(sed/E110)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "3933dec4-901e-0002-018a-056177000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:azure:DeviceManagement:DeviceInformation;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Device Information\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022manufacturer\u0022,\n", + " \u0022displayName\u0022: \u0022Manufacturer\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Company name of the device manufacturer. This could be the same as the name of the original equipment manufacturer (OEM). Ex. Contoso.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022model\u0022,\n", + " \u0022displayName\u0022: \u0022Device model\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Device model name or ID. Ex. Surface Book 2.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022swVersion\u0022,\n", + " \u0022displayName\u0022: \u0022Software version\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Version of the software on your device. This could be the version of your firmware. Ex. 1.3.45\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022osName\u0022,\n", + " \u0022displayName\u0022: \u0022Operating system name\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Name of the operating system on the device. Ex. Windows 10 IoT Core.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022processorArchitecture\u0022,\n", + " \u0022displayName\u0022: \u0022Processor architecture\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Architecture of the processor on the device. Ex. x64 or ARM.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022processorManufacturer\u0022,\n", + " \u0022displayName\u0022: \u0022Processor manufacturer\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Name of the manufacturer of the processor on the device. Ex. Intel.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022totalStorage\u0022,\n", + " \u0022displayName\u0022: \u0022Total storage\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022description\u0022: \u0022Total available storage on the device in kilobytes. Ex. 2048000 kilobytes.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022totalMemory\u0022,\n", + " \u0022displayName\u0022: \u0022Total memory\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022description\u0022: \u0022Total available memory on the device in kilobytes. Ex. 256000 kilobytes.\u0022\n", + " }\n", + " ]\n", + " }" + ] + } + ], + "Variables": { + "RandomSeed": "2046220415" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Remote)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Remote)Async.json new file mode 100644 index 000000000000..cc86d617c432 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDeps(Remote)Async.json @@ -0,0 +1,334 @@ +{ + "Entries": [ + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/temperaturecontroller-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-ec017cfa24c04a4fbe7250fccf6e06f7-a478d670ac944749-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "6ef2ca73131d88dfb4cb9839b8b49020", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "1762", + "Content-MD5": "w8OrFrbCwZwtVo6WaKDWxQ==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEABA3051\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:57 GMT", + "Server": [ + "ECAcc", + "(sed/E17F)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "8ae2e233-d01e-0092-748a-058600000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:TemperatureController;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Controller\u0022,\n", + " \u0022description\u0022: \u0022Device with two thermostats and remote reboot.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022DataSize\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022workingSet\u0022,\n", + " \u0022displayName\u0022: \u0022Working Set\u0022,\n", + " \u0022description\u0022: \u0022Current working set of the device memory in KiB.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022kibibyte\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022serialNumber\u0022,\n", + " \u0022displayName\u0022: \u0022Serial Number\u0022,\n", + " \u0022description\u0022: \u0022Serial number of the device.\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022reboot\u0022,\n", + " \u0022displayName\u0022: \u0022Reboot\u0022,\n", + " \u0022description\u0022: \u0022Reboots the device after waiting the number of seconds specified.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022delay\u0022,\n", + " \u0022displayName\u0022: \u0022Delay\u0022,\n", + " \u0022description\u0022: \u0022Number of seconds to wait before rebooting the device.\u0022,\n", + " \u0022schema\u0022: \u0022integer\u0022\n", + " }\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Component\u0022,\n", + " \u0022schema\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022name\u0022: \u0022thermostat1\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat One\u0022,\n", + " \u0022description\u0022: \u0022Thermostat One of Two.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Component\u0022,\n", + " \u0022schema\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022name\u0022: \u0022thermostat2\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat Two\u0022,\n", + " \u0022description\u0022: \u0022Thermostat Two of Two.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Component\u0022,\n", + " \u0022schema\u0022: \u0022dtmi:azure:DeviceManagement:DeviceInformation;1\u0022,\n", + " \u0022name\u0022: \u0022deviceInformation\u0022,\n", + " \u0022displayName\u0022: \u0022Device Information interface\u0022,\n", + " \u0022description\u0022: \u0022Optional interface with basic device hardware information.\u0022\n", + " }\n", + " ]\n", + "}" + ] + }, + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/thermostat-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-ec017cfa24c04a4fbe7250fccf6e06f7-394d03b5791fd04e-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "f4e554cd3571cb0d0888fba6831c0572", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "2469", + "Content-MD5": "U0VZgOgpfb6bwvG5UDVZuw==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEABC53BA\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:57 GMT", + "Server": [ + "ECAcc", + "(sed/E15D)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "7c45bf94-401e-006f-228a-05fc4e000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat\u0022,\n", + " \u0022description\u0022: \u0022Reports current temperature and provides desired temperature control.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022temperature\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature\u0022,\n", + " \u0022description\u0022: \u0022Temperature in degrees Celsius.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022targetTemperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022displayName\u0022: \u0022Target Temperature\u0022,\n", + " \u0022description\u0022: \u0022Allows to remotely specify the desired target temperature.\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022writable\u0022: true\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022maxTempSinceLastReboot\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature since last reboot.\u0022,\n", + " \u0022description\u0022: \u0022Returns the max temperature since last device reboot.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022getMaxMinReport\u0022,\n", + " \u0022displayName\u0022: \u0022Get Max-Min report.\u0022,\n", + " \u0022description\u0022: \u0022This command returns the max, min and average temperature from the specified time to the current time.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022since\u0022,\n", + " \u0022displayName\u0022: \u0022Since\u0022,\n", + " \u0022description\u0022: \u0022Period to return the max-min report.\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " \u0022response\u0022: {\n", + " \u0022name\u0022: \u0022tempReport\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Report\u0022,\n", + " \u0022schema\u0022: {\n", + " \u0022@type\u0022: \u0022Object\u0022,\n", + " \u0022fields\u0022: [\n", + " {\n", + " \u0022name\u0022: \u0022maxTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022minTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Min temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022avgTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Average Temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022startTime\u0022,\n", + " \u0022displayName\u0022: \u0022Start Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022endTime\u0022,\n", + " \u0022displayName\u0022: \u0022End Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " }\n", + " ]\n", + " }\n", + " }\n", + " }\n", + " ]\n", + "}" + ] + }, + { + "RequestUri": "https://devicemodels.azure.com/dtmi/azure/devicemanagement/deviceinformation-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-ec017cfa24c04a4fbe7250fccf6e06f7-a6a74c3922779a43-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "395ffea8b6c6fd04104a9d51bdc5fd00", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "2212", + "Content-MD5": "oCzHP9acH\u002B/\u002BFQOv2V56NA==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEBC614F0\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:59 GMT", + "Server": [ + "ECAcc", + "(sed/E110)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "3933dec4-901e-0002-018a-056177000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:azure:DeviceManagement:DeviceInformation;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Device Information\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022manufacturer\u0022,\n", + " \u0022displayName\u0022: \u0022Manufacturer\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Company name of the device manufacturer. This could be the same as the name of the original equipment manufacturer (OEM). Ex. Contoso.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022model\u0022,\n", + " \u0022displayName\u0022: \u0022Device model\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Device model name or ID. Ex. Surface Book 2.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022swVersion\u0022,\n", + " \u0022displayName\u0022: \u0022Software version\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Version of the software on your device. This could be the version of your firmware. Ex. 1.3.45\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022osName\u0022,\n", + " \u0022displayName\u0022: \u0022Operating system name\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Name of the operating system on the device. Ex. Windows 10 IoT Core.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022processorArchitecture\u0022,\n", + " \u0022displayName\u0022: \u0022Processor architecture\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Architecture of the processor on the device. Ex. x64 or ARM.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022processorManufacturer\u0022,\n", + " \u0022displayName\u0022: \u0022Processor manufacturer\u0022,\n", + " \u0022schema\u0022: \u0022string\u0022,\n", + " \u0022description\u0022: \u0022Name of the manufacturer of the processor on the device. Ex. Intel.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022totalStorage\u0022,\n", + " \u0022displayName\u0022: \u0022Total storage\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022description\u0022: \u0022Total available storage on the device in kilobytes. Ex. 2048000 kilobytes.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Property\u0022,\n", + " \u0022name\u0022: \u0022totalMemory\u0022,\n", + " \u0022displayName\u0022: \u0022Total memory\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022description\u0022: \u0022Total available memory on the device in kilobytes. Ex. 256000 kilobytes.\u0022\n", + " }\n", + " ]\n", + " }" + ] + } + ], + "Variables": { + "RandomSeed": "1313214083" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Local).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Local).json new file mode 100644 index 000000000000..c507bcc0e6c3 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Local).json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "317892805" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Local)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Local)Async.json new file mode 100644 index 000000000000..274a7a7d8d74 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Local)Async.json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "355414401" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Remote).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Remote).json new file mode 100644 index 000000000000..7a6fcb5b2129 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Remote).json @@ -0,0 +1,134 @@ +{ + "Entries": [ + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/thermostat-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-09d862b0af8400499f2d004928630aa9-20fa7316e50cfe49-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "f5cc9e497d9c30bfc661a702ecfd6a07", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "2469", + "Content-MD5": "U0VZgOgpfb6bwvG5UDVZuw==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEABC53BA\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:57 GMT", + "Server": [ + "ECAcc", + "(sed/E15D)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "7c45bf94-401e-006f-228a-05fc4e000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat\u0022,\n", + " \u0022description\u0022: \u0022Reports current temperature and provides desired temperature control.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022temperature\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature\u0022,\n", + " \u0022description\u0022: \u0022Temperature in degrees Celsius.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022targetTemperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022displayName\u0022: \u0022Target Temperature\u0022,\n", + " \u0022description\u0022: \u0022Allows to remotely specify the desired target temperature.\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022writable\u0022: true\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022maxTempSinceLastReboot\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature since last reboot.\u0022,\n", + " \u0022description\u0022: \u0022Returns the max temperature since last device reboot.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022getMaxMinReport\u0022,\n", + " \u0022displayName\u0022: \u0022Get Max-Min report.\u0022,\n", + " \u0022description\u0022: \u0022This command returns the max, min and average temperature from the specified time to the current time.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022since\u0022,\n", + " \u0022displayName\u0022: \u0022Since\u0022,\n", + " \u0022description\u0022: \u0022Period to return the max-min report.\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " \u0022response\u0022: {\n", + " \u0022name\u0022: \u0022tempReport\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Report\u0022,\n", + " \u0022schema\u0022: {\n", + " \u0022@type\u0022: \u0022Object\u0022,\n", + " \u0022fields\u0022: [\n", + " {\n", + " \u0022name\u0022: \u0022maxTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022minTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Min temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022avgTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Average Temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022startTime\u0022,\n", + " \u0022displayName\u0022: \u0022Start Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022endTime\u0022,\n", + " \u0022displayName\u0022: \u0022End Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " }\n", + " ]\n", + " }\n", + " }\n", + " }\n", + " ]\n", + "}" + ] + } + ], + "Variables": { + "RandomSeed": "1088420733" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Remote)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Remote)Async.json new file mode 100644 index 000000000000..95c316442f6e --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveSingleModelWithDepsDisableDependencyResolution(Remote)Async.json @@ -0,0 +1,134 @@ +{ + "Entries": [ + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/thermostat-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-08620b9b639adb448e2976b7c60e1940-0feae906a27dbb41-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "191089c1a8e22eefba5e136ffdfb9030", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "2469", + "Content-MD5": "U0VZgOgpfb6bwvG5UDVZuw==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEABC53BA\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:57 GMT", + "Server": [ + "ECAcc", + "(sed/E15D)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "7c45bf94-401e-006f-228a-05fc4e000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat\u0022,\n", + " \u0022description\u0022: \u0022Reports current temperature and provides desired temperature control.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022temperature\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature\u0022,\n", + " \u0022description\u0022: \u0022Temperature in degrees Celsius.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022targetTemperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022displayName\u0022: \u0022Target Temperature\u0022,\n", + " \u0022description\u0022: \u0022Allows to remotely specify the desired target temperature.\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022writable\u0022: true\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022maxTempSinceLastReboot\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature since last reboot.\u0022,\n", + " \u0022description\u0022: \u0022Returns the max temperature since last device reboot.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022getMaxMinReport\u0022,\n", + " \u0022displayName\u0022: \u0022Get Max-Min report.\u0022,\n", + " \u0022description\u0022: \u0022This command returns the max, min and average temperature from the specified time to the current time.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022since\u0022,\n", + " \u0022displayName\u0022: \u0022Since\u0022,\n", + " \u0022description\u0022: \u0022Period to return the max-min report.\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " \u0022response\u0022: {\n", + " \u0022name\u0022: \u0022tempReport\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Report\u0022,\n", + " \u0022schema\u0022: {\n", + " \u0022@type\u0022: \u0022Object\u0022,\n", + " \u0022fields\u0022: [\n", + " {\n", + " \u0022name\u0022: \u0022maxTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022minTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Min temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022avgTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Average Temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022startTime\u0022,\n", + " \u0022displayName\u0022: \u0022Start Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022endTime\u0022,\n", + " \u0022displayName\u0022: \u0022End Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " }\n", + " ]\n", + " }\n", + " }\n", + " }\n", + " ]\n", + "}" + ] + } + ], + "Variables": { + "RandomSeed": "774570438" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Local).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Local).json new file mode 100644 index 000000000000..a79b11d978ba --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Local).json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "737048842" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Local)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Local)Async.json new file mode 100644 index 000000000000..b6396d7c23e3 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Local)Async.json @@ -0,0 +1,6 @@ +{ + "Entries": [], + "Variables": { + "RandomSeed": "1545098366" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Remote).json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Remote).json new file mode 100644 index 000000000000..c0ffce5d0989 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Remote).json @@ -0,0 +1,134 @@ +{ + "Entries": [ + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/thermostat-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-2a3f314388159847b0cfdfeef0de35c8-025aa9e772e73f46-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "7d5aeeef70b4bb000098920bf8ed424f", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "2469", + "Content-MD5": "U0VZgOgpfb6bwvG5UDVZuw==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEABC53BA\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:57 GMT", + "Server": [ + "ECAcc", + "(sed/E15D)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "7c45bf94-401e-006f-228a-05fc4e000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat\u0022,\n", + " \u0022description\u0022: \u0022Reports current temperature and provides desired temperature control.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022temperature\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature\u0022,\n", + " \u0022description\u0022: \u0022Temperature in degrees Celsius.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022targetTemperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022displayName\u0022: \u0022Target Temperature\u0022,\n", + " \u0022description\u0022: \u0022Allows to remotely specify the desired target temperature.\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022writable\u0022: true\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022maxTempSinceLastReboot\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature since last reboot.\u0022,\n", + " \u0022description\u0022: \u0022Returns the max temperature since last device reboot.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022getMaxMinReport\u0022,\n", + " \u0022displayName\u0022: \u0022Get Max-Min report.\u0022,\n", + " \u0022description\u0022: \u0022This command returns the max, min and average temperature from the specified time to the current time.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022since\u0022,\n", + " \u0022displayName\u0022: \u0022Since\u0022,\n", + " \u0022description\u0022: \u0022Period to return the max-min report.\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " \u0022response\u0022: {\n", + " \u0022name\u0022: \u0022tempReport\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Report\u0022,\n", + " \u0022schema\u0022: {\n", + " \u0022@type\u0022: \u0022Object\u0022,\n", + " \u0022fields\u0022: [\n", + " {\n", + " \u0022name\u0022: \u0022maxTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022minTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Min temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022avgTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Average Temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022startTime\u0022,\n", + " \u0022displayName\u0022: \u0022Start Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022endTime\u0022,\n", + " \u0022displayName\u0022: \u0022End Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " }\n", + " ]\n", + " }\n", + " }\n", + " }\n", + " ]\n", + "}" + ] + } + ], + "Variables": { + "RandomSeed": "1926732807" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Remote)Async.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Remote)Async.json new file mode 100644 index 000000000000..cf8929894772 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/SessionRecords/ResolveIntegrationTests/ResolveWithWrongCasingThrowsException(Remote)Async.json @@ -0,0 +1,134 @@ +{ + "Entries": [ + { + "RequestUri": "https://devicemodels.azure.com/dtmi/com/example/thermostat-1.json", + "RequestMethod": "GET", + "RequestHeaders": { + "traceparent": "00-0f80a3c3b8b32b4cb5b2307d9e731d00-a6fe05d0fdb54049-00", + "User-Agent": [ + "azsdk-net-Iot.ModelsRepository/1.0.0-alpha.20210219.1", + "(.NET Core 4.6.29719.03; Microsoft Windows 10.0.19042 )" + ], + "x-ms-client-request-id": "5dd1b114246ef390d70b087417f3fcd4", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Accept-Ranges": "bytes", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", + "Access-Control-Expose-Headers": "*", + "Age": "155664", + "Content-Length": "2469", + "Content-MD5": "U0VZgOgpfb6bwvG5UDVZuw==", + "Content-Type": "application/json", + "Date": "Fri, 19 Feb 2021 19:24:50 GMT", + "ETag": "\u00220x8D8A13DEABC53BA\u0022", + "Last-Modified": "Tue, 15 Dec 2020 21:10:57 GMT", + "Server": [ + "ECAcc", + "(sed/E15D)" + ], + "X-Cache": "HIT", + "x-ms-error-code": "ConditionNotMet", + "x-ms-request-id": "7c45bf94-401e-006f-228a-05fc4e000000", + "x-ms-version": "2018-03-28" + }, + "ResponseBody": [ + "{\n", + " \u0022@context\u0022: \u0022dtmi:dtdl:context;2\u0022,\n", + " \u0022@id\u0022: \u0022dtmi:com:example:Thermostat;1\u0022,\n", + " \u0022@type\u0022: \u0022Interface\u0022,\n", + " \u0022displayName\u0022: \u0022Thermostat\u0022,\n", + " \u0022description\u0022: \u0022Reports current temperature and provides desired temperature control.\u0022,\n", + " \u0022contents\u0022: [\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Telemetry\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022temperature\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature\u0022,\n", + " \u0022description\u0022: \u0022Temperature in degrees Celsius.\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022targetTemperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022displayName\u0022: \u0022Target Temperature\u0022,\n", + " \u0022description\u0022: \u0022Allows to remotely specify the desired target temperature.\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022writable\u0022: true\n", + " },\n", + " {\n", + " \u0022@type\u0022: [\n", + " \u0022Property\u0022,\n", + " \u0022Temperature\u0022\n", + " ],\n", + " \u0022name\u0022: \u0022maxTempSinceLastReboot\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022,\n", + " \u0022unit\u0022: \u0022degreeCelsius\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature since last reboot.\u0022,\n", + " \u0022description\u0022: \u0022Returns the max temperature since last device reboot.\u0022\n", + " },\n", + " {\n", + " \u0022@type\u0022: \u0022Command\u0022,\n", + " \u0022name\u0022: \u0022getMaxMinReport\u0022,\n", + " \u0022displayName\u0022: \u0022Get Max-Min report.\u0022,\n", + " \u0022description\u0022: \u0022This command returns the max, min and average temperature from the specified time to the current time.\u0022,\n", + " \u0022request\u0022: {\n", + " \u0022name\u0022: \u0022since\u0022,\n", + " \u0022displayName\u0022: \u0022Since\u0022,\n", + " \u0022description\u0022: \u0022Period to return the max-min report.\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " \u0022response\u0022: {\n", + " \u0022name\u0022: \u0022tempReport\u0022,\n", + " \u0022displayName\u0022: \u0022Temperature Report\u0022,\n", + " \u0022schema\u0022: {\n", + " \u0022@type\u0022: \u0022Object\u0022,\n", + " \u0022fields\u0022: [\n", + " {\n", + " \u0022name\u0022: \u0022maxTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Max temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022minTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Min temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022avgTemp\u0022,\n", + " \u0022displayName\u0022: \u0022Average Temperature\u0022,\n", + " \u0022schema\u0022: \u0022double\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022startTime\u0022,\n", + " \u0022displayName\u0022: \u0022Start Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " },\n", + " {\n", + " \u0022name\u0022: \u0022endTime\u0022,\n", + " \u0022displayName\u0022: \u0022End Time\u0022,\n", + " \u0022schema\u0022: \u0022dateTime\u0022\n", + " }\n", + " ]\n", + " }\n", + " }\n", + " }\n", + " ]\n", + "}" + ] + } + ], + "Variables": { + "RandomSeed": "1193726475" + } +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/azure/devicemanagement/deviceinformation-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/azure/devicemanagement/deviceinformation-1.json new file mode 100644 index 000000000000..8a37e6d2c2c3 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/azure/devicemanagement/deviceinformation-1.json @@ -0,0 +1,64 @@ +{ + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:azure:DeviceManagement:DeviceInformation;1", + "@type": "Interface", + "displayName": "Device Information", + "contents": [ + { + "@type": "Property", + "name": "manufacturer", + "displayName": "Manufacturer", + "schema": "string", + "description": "Company name of the device manufacturer. This could be the same as the name of the original equipment manufacturer (OEM). Ex. Contoso." + }, + { + "@type": "Property", + "name": "model", + "displayName": "Device model", + "schema": "string", + "description": "Device model name or ID. Ex. Surface Book 2." + }, + { + "@type": "Property", + "name": "swVersion", + "displayName": "Software version", + "schema": "string", + "description": "Version of the software on your device. This could be the version of your firmware. Ex. 1.3.45" + }, + { + "@type": "Property", + "name": "osName", + "displayName": "Operating system name", + "schema": "string", + "description": "Name of the operating system on the device. Ex. Windows 10 IoT Core." + }, + { + "@type": "Property", + "name": "processorArchitecture", + "displayName": "Processor architecture", + "schema": "string", + "description": "Architecture of the processor on the device. Ex. x64 or ARM." + }, + { + "@type": "Property", + "name": "processorManufacturer", + "displayName": "Processor manufacturer", + "schema": "string", + "description": "Name of the manufacturer of the processor on the device. Ex. Intel." + }, + { + "@type": "Property", + "name": "totalStorage", + "displayName": "Total storage", + "schema": "double", + "description": "Total available storage on the device in kilobytes. Ex. 2048000 kilobytes." + }, + { + "@type": "Property", + "name": "totalMemory", + "displayName": "Total memory", + "schema": "double", + "description": "Total available memory on the device in kilobytes. Ex. 256000 kilobytes." + } + ] +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/azure/devicemanagement/deviceinformation-2.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/azure/devicemanagement/deviceinformation-2.json new file mode 100644 index 000000000000..d35b8a3e3a1d --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/azure/devicemanagement/deviceinformation-2.json @@ -0,0 +1,16 @@ +{ + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:azure:DeviceManagement:DeviceInformation;2", + "@type": "Interface", + "extends": "dtmi:azure:DeviceManagement:DeviceInformation;1", + "displayName": "Device Information", + "contents": [ + { + "@type": "Property", + "name": "osKernelVersion", + "displayName": "OS Kernel Version", + "schema": "string", + "description": "OS Kernel Version. Ex. Linux 4.15.0-54-generic x86_64." + } + ] +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/base-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/base-1.json new file mode 100644 index 000000000000..85424e8a229e --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/base-1.json @@ -0,0 +1,56 @@ +{ + "@id": "dtmi:com:example:base;1", + "@type": "Interface", + "contents": [ + { + "@type": "Property", + "name": "baseSerialNumber", + "schema": "string" + } + ], + "displayName": { + "en": "mybaseProp" + }, + "extends": [ + { + "@id": "dtmi:com:example:basic;1", + "@type": "Interface", + "contents": [ + { + "@type": "Property", + "name": "serialNumber", + "schema": "string", + "writable": false + }, + { + "@type": [ + "Telemetry", + "Temperature" + ], + "displayName": { + "en": "temperature" + }, + "name": "temperature", + "schema": "double", + "unit": "degreeCelsius" + }, + { + "@type": "Property", + "displayName": { + "en": "targetTemperature" + }, + "name": "targetTemperature", + "schema": "double", + "writable": true + } + ], + "displayName": { + "en": "Basic" + } + } + ], + "@context": [ + "dtmi:iotcentral:context;2", + "dtmi:dtdl:context;2" + ] +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/base-2.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/base-2.json new file mode 100644 index 000000000000..29accafcc252 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/base-2.json @@ -0,0 +1,57 @@ +{ + "@id": "dtmi:com:example:base;2", + "@type": "Interface", + "contents": [ + { + "@type": "Property", + "name": "baseSerialNumber", + "schema": "string" + } + ], + "displayName": { + "en": "mybaseProp" + }, + "extends": [ + { + "@id": "dtmi:com:example:basic;1", + "@type": "Interface", + "contents": [ + { + "@type": "Property", + "name": "serialNumber", + "schema": "string", + "writable": false + }, + { + "@type": [ + "Telemetry", + "Temperature" + ], + "displayName": { + "en": "temperature" + }, + "name": "temperature", + "schema": "double", + "unit": "degreeCelsius" + }, + { + "@type": "Property", + "displayName": { + "en": "targetTemperature" + }, + "name": "targetTemperature", + "schema": "double", + "writable": true + } + ], + "displayName": { + "en": "Basic" + } + }, + "dtmi:com:example:Freezer;1" + ], + "@context": [ + "dtmi:iotcentral:context;2", + "dtmi:dtdl:context;2" + ] +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/building-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/building-1.json new file mode 100644 index 000000000000..c8b6bc6f7375 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/building-1.json @@ -0,0 +1,19 @@ +{ + "@id": "dtmi:com:example:Building;1", + "@type": "Interface", + "displayName": "Building", + "contents": [ + { + "@type": "Property", + "name": "name", + "schema": "string", + "writable": true + }, + { + "@type": "Relationship", + "name": "contains", + "target": "dtmi:com:example:Room;1" + } + ], + "@context": "dtmi:dtdl:context;2" +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/camera-3.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/camera-3.json new file mode 100644 index 000000000000..f912746c0040 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/camera-3.json @@ -0,0 +1,13 @@ +{ + "@id": "dtmi:com:example:Camera;3", + "@type": "Interface", + "displayName": "Phone", + "contents": [ + { + "@type": "Component", + "name": "deviceInfo", + "schema": "dtmi:azure:DeviceManagement:DeviceInformation;1" + } + ], + "@context": "dtmi:dtdl:context;2" +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/coldstorage-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/coldstorage-1.json new file mode 100644 index 000000000000..a3b8466118a9 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/coldstorage-1.json @@ -0,0 +1,13 @@ +{ + "@id": "dtmi:com:example:ColdStorage;1", + "@type": "Interface", + "extends": ["dtmi:com:example:Room;1", "dtmi:com:example:Freezer;1"], + "contents": [ + { + "@type": "Property", + "name": "capacity", + "schema": "integer" + } + ], + "@context": "dtmi:dtdl:context;2" +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/conferenceroom-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/conferenceroom-1.json new file mode 100644 index 000000000000..2e756ee73b6e --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/conferenceroom-1.json @@ -0,0 +1,13 @@ +{ + "@id": "dtmi:com:example:ConferenceRoom;1", + "@type": "Interface", + "extends": "dtmi:com:example:Room;1", + "contents": [ + { + "@type": "Property", + "name": "capacity", + "schema": "integer" + } + ], + "@context": "dtmi:dtdl:context;2" +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/freezer-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/freezer-1.json new file mode 100644 index 000000000000..6006b6673299 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/freezer-1.json @@ -0,0 +1,12 @@ +{ + "@id": "dtmi:com:example:Freezer;1", + "@type": "Interface", + "contents": [ + { + "@type": "Component", + "name": "deviceInfo", + "schema": "dtmi:com:example:Thermostat;1" + } + ], + "@context": "dtmi:dtdl:context;2" +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/incompleteexpanded-1.expanded.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/incompleteexpanded-1.expanded.json new file mode 100644 index 000000000000..e91626b56e44 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/incompleteexpanded-1.expanded.json @@ -0,0 +1,151 @@ +[ + { + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:com:example:IncompleteExpanded;1", + "@type": "Interface", + "displayName": "Incomplete Expanded Temperature Controller", + "description": "Device with two thermostats and remote reboot.", + "contents": [ + { + "@type": [ + "Telemetry", + "DataSize" + ], + "name": "workingSet", + "displayName": "Working Set", + "description": "Current working set of the device memory in KiB.", + "schema": "double", + "unit": "kibibyte" + }, + { + "@type": "Property", + "name": "serialNumber", + "displayName": "Serial Number", + "description": "Serial number of the device.", + "schema": "string" + }, + { + "@type": "Command", + "name": "reboot", + "displayName": "Reboot", + "description": "Reboots the device after waiting the number of seconds specified.", + "request": { + "name": "delay", + "displayName": "Delay", + "description": "Number of seconds to wait before rebooting the device.", + "schema": "integer" + } + }, + { + "@type": "Component", + "schema": "dtmi:com:example:Thermostat;1", + "name": "thermostat1", + "displayName": "Thermostat One", + "description": "Thermostat One of Two." + }, + { + "@type": "Component", + "schema": "dtmi:com:example:Thermostat;1", + "name": "thermostat2", + "displayName": "Thermostat Two", + "description": "Thermostat Two of Two." + }, + { + "@type": "Component", + "schema": "dtmi:azure:DeviceManagement:DeviceInformation;1", + "name": "deviceInformation", + "displayName": "Device Information interface", + "description": "Optional interface with basic device hardware information." + } + ] + }, + { + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:com:example:Thermostat;1", + "@type": "Interface", + "displayName": "Thermostat", + "description": "Reports current temperature and provides desired temperature control.", + "contents": [ + { + "@type": [ + "Telemetry", + "Temperature" + ], + "name": "temperature", + "displayName": "Temperature", + "description": "Temperature in degrees Celsius.", + "schema": "double", + "unit": "degreeCelsius" + }, + { + "@type": [ + "Property", + "Temperature" + ], + "name": "targetTemperature", + "schema": "double", + "displayName": "Target Temperature", + "description": "Allows to remotely specify the desired target temperature.", + "unit": "degreeCelsius", + "writable": true + }, + { + "@type": [ + "Property", + "Temperature" + ], + "name": "maxTempSinceLastReboot", + "schema": "double", + "unit": "degreeCelsius", + "displayName": "Max temperature since last reboot.", + "description": "Returns the max temperature since last device reboot." + }, + { + "@type": "Command", + "name": "getMaxMinReport", + "displayName": "Get Max-Min report.", + "description": "This command returns the max, min and average temperature from the specified time to the current time.", + "request": { + "name": "since", + "displayName": "Since", + "description": "Period to return the max-min report.", + "schema": "dateTime" + }, + "response": { + "name": "tempReport", + "displayName": "Temperature Report", + "schema": { + "@type": "Object", + "fields": [ + { + "name": "maxTemp", + "displayName": "Max temperature", + "schema": "double" + }, + { + "name": "minTemp", + "displayName": "Min temperature", + "schema": "double" + }, + { + "name": "avgTemp", + "displayName": "Average Temperature", + "schema": "double" + }, + { + "name": "startTime", + "displayName": "Start Time", + "schema": "dateTime" + }, + { + "name": "endTime", + "displayName": "End Time", + "schema": "dateTime" + } + ] + } + } + } + ] + } +] \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/invalidmodel-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/invalidmodel-1.json new file mode 100644 index 000000000000..4f18d7b17658 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/invalidmodel-1.json @@ -0,0 +1,13 @@ +{ + "@id": "dtmi:com:example:invalidmodel;1", + "@type": "Interface", + "displayName": "Phone", + "contents": [ + { + "@type": "Component", + "name": "deviceInfo", + "schema": "dtmi:azure:fakeDeviceManagement:FakeDeviceInformation;2" + } + ], + "@context": "dtmi:dtdl:context;2" +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/invalidmodel-2.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/invalidmodel-2.json new file mode 100644 index 000000000000..61443734cd90 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/invalidmodel-2.json @@ -0,0 +1,23 @@ +{ + "@id": "dtmi:com:example:Phone;2", + "@type": "Interfacez", + "displayName": "Phone", + "contentsz": [ + { + "@type": "Component", + "name": "frontCamera", + "schema": "dtmi:com:example:Camera;3" + }, + { + "@type": "Component", + "name": "backCamera", + "schema": "dtmi:com:example:Camera;3" + }, + { + "@type": "Component", + "name": "deviceInfo", + "schema": "dtmi:azure:deviceManagement:DeviceInformation;2" + } + ], + "@context": "dtmi:dtdl:context;2" +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/phone-2.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/phone-2.json new file mode 100644 index 000000000000..26c7efbdedc0 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/phone-2.json @@ -0,0 +1,23 @@ +{ + "@id": "dtmi:com:example:Phone;2", + "@type": "Interface", + "displayName": "Phone", + "contents": [ + { + "@type": "Component", + "name": "frontCamera", + "schema": "dtmi:com:example:Camera;3" + }, + { + "@type": "Component", + "name": "backCamera", + "schema": "dtmi:com:example:Camera;3" + }, + { + "@type": "Component", + "name": "deviceInfo", + "schema": "dtmi:azure:DeviceManagement:DeviceInformation;2" + } + ], + "@context": "dtmi:dtdl:context;2" +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/room-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/room-1.json new file mode 100644 index 000000000000..1a07edec4d98 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/room-1.json @@ -0,0 +1,12 @@ +{ + "@id": "dtmi:com:example:Room;1", + "@type": "Interface", + "contents": [ + { + "@type": "Property", + "name": "occupied", + "schema": "boolean" + } + ], + "@context": "dtmi:dtdl:context;2" +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/temperaturecontroller-1.expanded.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/temperaturecontroller-1.expanded.json new file mode 100644 index 000000000000..14e8e294189e --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/temperaturecontroller-1.expanded.json @@ -0,0 +1,215 @@ +[ + { + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:com:example:TemperatureController;1", + "@type": "Interface", + "displayName": "Temperature Controller", + "description": "Device with two thermostats and remote reboot.", + "contents": [ + { + "@type": [ + "Telemetry", + "DataSize" + ], + "name": "workingSet", + "displayName": "Working Set", + "description": "Current working set of the device memory in KiB.", + "schema": "double", + "unit": "kibibyte" + }, + { + "@type": "Property", + "name": "serialNumber", + "displayName": "Serial Number", + "description": "Serial number of the device.", + "schema": "string" + }, + { + "@type": "Command", + "name": "reboot", + "displayName": "Reboot", + "description": "Reboots the device after waiting the number of seconds specified.", + "request": { + "name": "delay", + "displayName": "Delay", + "description": "Number of seconds to wait before rebooting the device.", + "schema": "integer" + } + }, + { + "@type": "Component", + "schema": "dtmi:com:example:Thermostat;1", + "name": "thermostat1", + "displayName": "Thermostat One", + "description": "Thermostat One of Two." + }, + { + "@type": "Component", + "schema": "dtmi:com:example:Thermostat;1", + "name": "thermostat2", + "displayName": "Thermostat Two", + "description": "Thermostat Two of Two." + }, + { + "@type": "Component", + "schema": "dtmi:azure:DeviceManagement:DeviceInformation;1", + "name": "deviceInformation", + "displayName": "Device Information interface", + "description": "Optional interface with basic device hardware information." + } + ] + }, + { + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:com:example:Thermostat;1", + "@type": "Interface", + "displayName": "Thermostat", + "description": "Reports current temperature and provides desired temperature control.", + "contents": [ + { + "@type": [ + "Telemetry", + "Temperature" + ], + "name": "temperature", + "displayName": "Temperature", + "description": "Temperature in degrees Celsius.", + "schema": "double", + "unit": "degreeCelsius" + }, + { + "@type": [ + "Property", + "Temperature" + ], + "name": "targetTemperature", + "schema": "double", + "displayName": "Target Temperature", + "description": "Allows to remotely specify the desired target temperature.", + "unit": "degreeCelsius", + "writable": true + }, + { + "@type": [ + "Property", + "Temperature" + ], + "name": "maxTempSinceLastReboot", + "schema": "double", + "unit": "degreeCelsius", + "displayName": "Max temperature since last reboot.", + "description": "Returns the max temperature since last device reboot." + }, + { + "@type": "Command", + "name": "getMaxMinReport", + "displayName": "Get Max-Min report.", + "description": "This command returns the max, min and average temperature from the specified time to the current time.", + "request": { + "name": "since", + "displayName": "Since", + "description": "Period to return the max-min report.", + "schema": "dateTime" + }, + "response": { + "name": "tempReport", + "displayName": "Temperature Report", + "schema": { + "@type": "Object", + "fields": [ + { + "name": "maxTemp", + "displayName": "Max temperature", + "schema": "double" + }, + { + "name": "minTemp", + "displayName": "Min temperature", + "schema": "double" + }, + { + "name": "avgTemp", + "displayName": "Average Temperature", + "schema": "double" + }, + { + "name": "startTime", + "displayName": "Start Time", + "schema": "dateTime" + }, + { + "name": "endTime", + "displayName": "End Time", + "schema": "dateTime" + } + ] + } + } + } + ] + }, + { + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:azure:DeviceManagement:DeviceInformation;1", + "@type": "Interface", + "displayName": "Device Information", + "contents": [ + { + "@type": "Property", + "name": "manufacturer", + "displayName": "Manufacturer", + "schema": "string", + "description": "Company name of the device manufacturer. This could be the same as the name of the original equipment manufacturer (OEM). Ex. Contoso." + }, + { + "@type": "Property", + "name": "model", + "displayName": "Device model", + "schema": "string", + "description": "Device model name or ID. Ex. Surface Book 2." + }, + { + "@type": "Property", + "name": "swVersion", + "displayName": "Software version", + "schema": "string", + "description": "Version of the software on your device. This could be the version of your firmware. Ex. 1.3.45" + }, + { + "@type": "Property", + "name": "osName", + "displayName": "Operating system name", + "schema": "string", + "description": "Name of the operating system on the device. Ex. Windows 10 IoT Core." + }, + { + "@type": "Property", + "name": "processorArchitecture", + "displayName": "Processor architecture", + "schema": "string", + "description": "Architecture of the processor on the device. Ex. x64 or ARM." + }, + { + "@type": "Property", + "name": "processorManufacturer", + "displayName": "Processor manufacturer", + "schema": "string", + "description": "Name of the manufacturer of the processor on the device. Ex. Intel." + }, + { + "@type": "Property", + "name": "totalStorage", + "displayName": "Total storage", + "schema": "double", + "description": "Total available storage on the device in kilobytes. Ex. 2048000 kilobytes." + }, + { + "@type": "Property", + "name": "totalMemory", + "displayName": "Total memory", + "schema": "double", + "description": "Total available memory on the device in kilobytes. Ex. 256000 kilobytes." + } + ] + } +] \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/temperaturecontroller-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/temperaturecontroller-1.json new file mode 100644 index 000000000000..c455ddf8bae6 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/temperaturecontroller-1.json @@ -0,0 +1,60 @@ +{ + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:com:example:TemperatureController;1", + "@type": "Interface", + "displayName": "Temperature Controller", + "description": "Device with two thermostats and remote reboot.", + "contents": [ + { + "@type": [ + "Telemetry", + "DataSize" + ], + "name": "workingSet", + "displayName": "Working Set", + "description": "Current working set of the device memory in KiB.", + "schema": "double", + "unit": "kibibyte" + }, + { + "@type": "Property", + "name": "serialNumber", + "displayName": "Serial Number", + "description": "Serial number of the device.", + "schema": "string" + }, + { + "@type": "Command", + "name": "reboot", + "displayName": "Reboot", + "description": "Reboots the device after waiting the number of seconds specified.", + "request": { + "name": "delay", + "displayName": "Delay", + "description": "Number of seconds to wait before rebooting the device.", + "schema": "integer" + } + }, + { + "@type": "Component", + "schema": "dtmi:com:example:Thermostat;1", + "name": "thermostat1", + "displayName": "Thermostat One", + "description": "Thermostat One of Two." + }, + { + "@type": "Component", + "schema": "dtmi:com:example:Thermostat;1", + "name": "thermostat2", + "displayName": "Thermostat Two", + "description": "Thermostat Two of Two." + }, + { + "@type": "Component", + "schema": "dtmi:azure:DeviceManagement:DeviceInformation;1", + "name": "deviceInformation", + "displayName": "Device Information interface", + "description": "Optional interface with basic device hardware information." + } + ] +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/thermostat-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/thermostat-1.json new file mode 100644 index 000000000000..315a307bbcb3 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/com/example/thermostat-1.json @@ -0,0 +1,19 @@ +{ + "@id": "dtmi:com:example:Thermostat;1", + "@type": "Interface", + "displayName": "Thermostat", + "contents": [ + { + "@type": "Telemetry", + "name": "temp", + "schema": "double" + }, + { + "@type": "Property", + "name": "setPointTemp", + "writable": true, + "schema": "double" + } + ], + "@context": "dtmi:dtdl:context;2" +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/company/demodevice-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/company/demodevice-1.json new file mode 100644 index 000000000000..33c9554664a6 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/company/demodevice-1.json @@ -0,0 +1,31 @@ +{ + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:company:demodevice;1", + "@type": "Interface", + "displayName": "demodevice", + "contents": [ + { + "@type": "Component", + "name": "c1", + "schema": "dtmi:azure:deviceManagement:DeviceInformation;1" + }, + { + "@type": "Telemetry", + "name": "temperature", + "schema": "double" + }, + { + "@type": "Property", + "name": "deviceStatus", + "schema": "string" + }, + { + "@type": "Command", + "name": "reboot", + "request": { + "name": "delay", + "schema": "integer" + } + } + ] +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/company/demodevice-2.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/company/demodevice-2.json new file mode 100644 index 000000000000..9d9b3bd2322a --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/company/demodevice-2.json @@ -0,0 +1,31 @@ +{ + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:company:demodevice;1", + "@type": "Interface", + "displayName": "demodevice", + "contents": [ + { + "@type": "Component", + "name": "c1", + "schema": "dtmi:azure:DeviceManagement:DeviceInformation;1" + }, + { + "@type": "Telemetry", + "name": "temperature", + "schema": "double" + }, + { + "@type": "Property", + "name": "deviceStatus", + "schema": "string" + }, + { + "@type": "Command", + "name": "reboot", + "request": { + "name": "delay", + "schema": "integer" + } + } + ] +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/badfilepath-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/badfilepath-1.json new file mode 100644 index 000000000000..6006b6673299 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/badfilepath-1.json @@ -0,0 +1,12 @@ +{ + "@id": "dtmi:com:example:Freezer;1", + "@type": "Interface", + "contents": [ + { + "@type": "Component", + "name": "deviceInfo", + "schema": "dtmi:com:example:Thermostat;1" + } + ], + "@context": "dtmi:dtdl:context;2" +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/emptyarray-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/emptyarray-1.json new file mode 100644 index 000000000000..0637a088a01e --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/emptyarray-1.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/namespaceconflict-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/namespaceconflict-1.json new file mode 100644 index 000000000000..6f2df812a67f --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/namespaceconflict-1.json @@ -0,0 +1,37 @@ +{ + "@id": "dtmi:strict:namespaceconflict;1", + "@type": "Interface", + "contents": [ + { + "@type": "Telemetry", + "name": "accelerometer1", + "schema": "dtmi:com:example:acceleration;1" + }, + { + "@type": "Telemetry", + "name": "accelerometer2", + "schema": "dtmi:com:example:acceleration;1" + } + ], + "schemas": [ + { + "@id": "dtmi:com:example:acceleration;1", + "@type": "Object", + "fields": [ + { + "name": "x", + "schema": "double" + }, + { + "name": "y", + "schema": "double" + }, + { + "name": "z", + "schema": "double" + } + ] + } + ], + "@context": "dtmi:dtdl:context;2" +} \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/nondtdl-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/nondtdl-1.json new file mode 100644 index 000000000000..b25ba2ac3af6 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/nondtdl-1.json @@ -0,0 +1 @@ +"content" \ No newline at end of file diff --git a/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/unsupportedrootarray-1.json b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/unsupportedrootarray-1.json new file mode 100644 index 000000000000..1f282307a8c3 --- /dev/null +++ b/sdk/modelsrepository/Azure.Iot.ModelsRepository/tests/TestModelRepo/dtmi/strict/unsupportedrootarray-1.json @@ -0,0 +1,91 @@ +[ + { + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:strict:unsupportedrootarray;1", + "@type": "Interface", + "displayName": "Thermostat", + "description": "Reports current temperature and provides desired temperature control.", + "contents": [ + { + "@type": [ + "Telemetry", + "Temperature" + ], + "name": "temperature", + "displayName": "Temperature", + "description": "Temperature in degrees Celsius.", + "schema": "double", + "unit": "degreeCelsius" + }, + { + "@type": [ + "Property", + "Temperature" + ], + "name": "targetTemperature", + "schema": "double", + "displayName": "Target Temperature", + "description": "Allows to remotely specify the desired target temperature.", + "unit": "degreeCelsius", + "writable": true + }, + { + "@type": [ + "Property", + "Temperature" + ], + "name": "maxTempSinceLastReboot", + "schema": "double", + "unit": "degreeCelsius", + "displayName": "Max temperature since last reboot.", + "description": "Returns the max temperature since last device reboot." + }, + { + "@type": "Command", + "name": "getMaxMinReport", + "displayName": "Get Max-Min report.", + "description": "This command returns the max, min and average temperature from the specified time to the current time.", + "request": { + "name": "since", + "displayName": "Since", + "description": "Period to return the max-min report.", + "schema": "dateTime" + }, + "response": { + "name": "tempReport", + "displayName": "Temperature Report", + "schema": { + "@type": "Object", + "fields": [ + { + "name": "maxTemp", + "displayName": "Max temperature", + "schema": "double" + }, + { + "name": "minTemp", + "displayName": "Min temperature", + "schema": "double" + }, + { + "name": "avgTemp", + "displayName": "Average Temperature", + "schema": "double" + }, + { + "name": "startTime", + "displayName": "Start Time", + "schema": "dateTime" + }, + { + "name": "endTime", + "displayName": "End Time", + "schema": "dateTime" + } + ] + } + } + } + ] + } +] \ No newline at end of file diff --git a/sdk/modelsrepository/ci.yml b/sdk/modelsrepository/ci.yml new file mode 100644 index 000000000000..98dfbccfca59 --- /dev/null +++ b/sdk/modelsrepository/ci.yml @@ -0,0 +1,32 @@ +# NOTE: Please refer to https://aka.ms/azsdk/engsys/ci-yaml before editing this file. + +trigger: + branches: + include: + - master + - hotfix/* + - release/* + paths: + include: + - sdk/modelsrepository/ + +pr: + branches: + include: + - master + - feature/* + - hotfix/* + - release/* + paths: + include: + - sdk/modelsrepository/ + +extends: + template: ../../eng/pipelines/templates/stages/archetype-sdk-client.yml + parameters: + ServiceDirectory: modelsrepository + ArtifactName: packages + Artifacts: + - name: Azure.IoT.ModelsRepository + safeName: AzureIoTModelsRepository + diff --git a/sdk/modelsrepository/tests.yml b/sdk/modelsrepository/tests.yml new file mode 100644 index 000000000000..235a94db0279 --- /dev/null +++ b/sdk/modelsrepository/tests.yml @@ -0,0 +1,8 @@ +trigger: none + +extends: + template: ../../eng/pipelines/templates/stages/archetype-sdk-tests.yml + parameters: + ServiceDirectory: modelsrepository + Location: westus2 + Clouds: Preview