This commit is contained in:
Nate Malubay 2019-11-12 17:28:05 -08:00
Коммит 73bca2d926
58 изменённых файлов: 3013 добавлений и 0 удалений

78
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,78 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Mac OS generated
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
## Build generated
build/
DerivedData/
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
.build/
.swiftpm/
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output

9
CODE_OF_CONDUCT.md Normal file
Просмотреть файл

@ -0,0 +1,9 @@
# Microsoft Open Source Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
Resources:
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns

24
CONTRIBUTING.md Normal file
Просмотреть файл

@ -0,0 +1,24 @@
# Contributing to IomtFhirClient
This document describes guidelines for contributing to the IomtFhirClient repo.
## Submitting Pull Requests
- **DO** submit all changes via pull requests (PRs). They will be reviewed and potentially be merged by maintainers after a peer review that includes at least one of the team members.
- **DO** give PRs short but descriptive names.
- **DO** write a useful but brief description of what the PR is for.
- **DO** refer to any relevant issues and use [keywords](https://help.github.com/articles/closing-issues-using-keywords/) that automatically close issues when the PR is merged.
- **DO** ensure each commit successfully builds. The entire PR must pass all checks before it will be merged.
- **DO** address PR feedback in additional commits instead of amending.
- **DO** assume that [Squash and Merge](https://blog.github.com/2016-04-01-squash-your-commits/) will be used to merge the commits unless specifically requested otherwise.
- **DO NOT** submit "work in progress" PRs. A PR should only be submitted when it is considered ready for review.
- **DO NOT** mix independent and unrelated changes in one PR.
## Creating Issues
- **DO** use a descriptive title that identifies the issue or the requested feature.
- **DO** write a detailed description of the issue or the requested feature.
- **DO** provide details for issues you create:
- Describe the expected and actual behavior.
- Provide any relevant exception message or OperationOutcome.
- **DO** subscribe to notifications for created issues in case there are any follow-up questions.

23
GeoPol.xml Normal file
Просмотреть файл

@ -0,0 +1,23 @@
<!-- List of Include Files and folders and exception lists for this repo for GeoPolitical scanning. Contact Joerow for detail. -->
<!-- Consult Global Readiness Notebook @ aka.ms/NExTGeoPol for further details -->
<!-- This file is consumed by PowerShell scripts in the 'health-localization' repo under the LocBuild\GeoPolitical folder(s) -->
<!DOCTYPE varsdefined [
<!ENTITY GitReposFolder "C:\GITs\Repos">
<!ENTITY GitRepoName "readmissions-event-hubs-ios">
]>
<GeoPol_Folders>
<!-- List of Folders to include for GeoPolitical scanning -->
<GitRepoName>&GitRepoName;</GitRepoName>
<Component Include="List here folders to Include in a GeoPol Scan">
<!-- . means the entire repo -->
<!-- Use back slash \ to indicate folder path e.g. C:\Temp\Git\ -->
<IncludeFolder>.</IncludeFolder>
</Component>
<Component Exclude="List exceptions here to not be scanned, that have been included above">
<!-- Make sure to consult http://aka.ms/NExtStart if excluding 3rd party or OSS components -->
<!-- Use back slash \ to indicate folder path e.g. C:\Temp\Git\ -->
<ExcludeFolder>.gitignore</ExcludeFolder>
<ExcludeFolder>GeoPol.xml</ExcludeFolder>
</Component>
</GeoPol_Folders>

Просмотреть файл

@ -0,0 +1,551 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objects = {
/* Begin PBXBuildFile section */
A52BB53F2346970200650972 /* IomtFhirClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52BB53E2346970200650972 /* IomtFhirClientTests.swift */; };
A52BB541234697B400650972 /* IomtFhirClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52BB540234697B400650972 /* IomtFhirClient.swift */; };
A52BB543234697F500650972 /* IomtFhirClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52BB542234697F500650972 /* IomtFhirClientError.swift */; };
A52BB5452346986200650972 /* HttpIomtFhirClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52BB5442346986200650972 /* HttpIomtFhirClient.swift */; };
A55FD907231059AC003E6220 /* MockDataSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55FD906231059AC003E6220 /* MockDataSender.swift */; };
A55FD9092310823A003E6220 /* MockError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55FD9082310823A003E6220 /* MockError.swift */; };
A55FD90B23108C68003E6220 /* UrlExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55FD90A23108C68003E6220 /* UrlExtensionsTests.swift */; };
A57A678D234650C9006BFEEE /* ConnectionManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A6771234650C9006BFEEE /* ConnectionManagerProtocol.swift */; };
A57A678E234650C9006BFEEE /* EventData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A6772234650C9006BFEEE /* EventData.swift */; };
A57A678F234650C9006BFEEE /* EventDataSenderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A6773234650C9006BFEEE /* EventDataSenderProtocol.swift */; };
A57A6790234650C9006BFEEE /* EventDataSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A6774234650C9006BFEEE /* EventDataSender.swift */; };
A57A6792234650C9006BFEEE /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A6776234650C9006BFEEE /* ConnectionManager.swift */; };
A57A6793234650C9006BFEEE /* UrlExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A6778234650C9006BFEEE /* UrlExtensions.swift */; };
A57A6794234650C9006BFEEE /* TransportType.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A677A234650C9006BFEEE /* TransportType.swift */; };
A57A6795234650C9006BFEEE /* TokenProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A677B234650C9006BFEEE /* TokenProviderProtocol.swift */; };
A57A6796234650C9006BFEEE /* DateFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A677C234650C9006BFEEE /* DateFactory.swift */; };
A57A6797234650C9006BFEEE /* EventHubsTokenError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A677D234650C9006BFEEE /* EventHubsTokenError.swift */; };
A57A6798234650C9006BFEEE /* SecurityToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A677E234650C9006BFEEE /* SecurityToken.swift */; };
A57A679A234650C9006BFEEE /* EventHubsMessageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A6780234650C9006BFEEE /* EventHubsMessageError.swift */; };
A57A679B234650C9006BFEEE /* EventHubsConnectionStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A6781234650C9006BFEEE /* EventHubsConnectionStringBuilder.swift */; };
A57A679C234650C9006BFEEE /* SharedAccessSignatureTokenProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A6782234650C9006BFEEE /* SharedAccessSignatureTokenProvider.swift */; };
A57A679D234650C9006BFEEE /* DateFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A6783234650C9006BFEEE /* DateFactoryProtocol.swift */; };
A57A679E234650C9006BFEEE /* TokenScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A6784234650C9006BFEEE /* TokenScope.swift */; };
A57A679F234650C9006BFEEE /* SharedAccessSignatureToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A6785234650C9006BFEEE /* SharedAccessSignatureToken.swift */; };
A57A67A0234650C9006BFEEE /* HttpEventDataSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A6787234650C9006BFEEE /* HttpEventDataSender.swift */; };
A57A67A1234650C9006BFEEE /* HttpMessageConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A6788234650C9006BFEEE /* HttpMessageConverter.swift */; };
A57A67A3234650C9006BFEEE /* HttpMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A678A234650C9006BFEEE /* HttpMessage.swift */; };
A57A67A4234650C9006BFEEE /* HttpEventHubsConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A678B234650C9006BFEEE /* HttpEventHubsConnection.swift */; };
A57A67A5234650C9006BFEEE /* EventHubsConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57A678C234650C9006BFEEE /* EventHubsConnection.swift */; };
A57A67A823465159006BFEEE /* Quick in Frameworks */ = {isa = PBXBuildFile; productRef = A57A67A723465159006BFEEE /* Quick */; };
A57A67AB2346518A006BFEEE /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = A57A67AA2346518A006BFEEE /* Nimble */; };
A58684F923148E67003FEC6A /* HttpEventDataSenderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58684F823148E67003FEC6A /* HttpEventDataSenderTests.swift */; };
A58684FB23148E8A003FEC6A /* HttpEventHubsConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58684FA23148E8A003FEC6A /* HttpEventHubsConnectionTests.swift */; };
A58684FD23148EA5003FEC6A /* HttpMessageConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58684FC23148EA5003FEC6A /* HttpMessageConverterTests.swift */; };
A58684FF23148EBE003FEC6A /* EventHubsConnectionStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58684FE23148EBE003FEC6A /* EventHubsConnectionStringBuilderTests.swift */; };
A586850123148ED9003FEC6A /* SharedAccessSignatureTokenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A586850023148ED9003FEC6A /* SharedAccessSignatureTokenTests.swift */; };
A586850323148EF0003FEC6A /* SharedAccessSignatureTokenProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A586850223148EF0003FEC6A /* SharedAccessSignatureTokenProviderTests.swift */; };
A58685052314947D003FEC6A /* MockConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58685042314947D003FEC6A /* MockConnectionManager.swift */; };
A58685072314B59D003FEC6A /* MockHttpEventHubsConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58685062314B59D003FEC6A /* MockHttpEventHubsConnection.swift */; };
A58685092314D4E0003FEC6A /* MockURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58685082314D4E0003FEC6A /* MockURLSession.swift */; };
A586850B2314D6B1003FEC6A /* MockTokenProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A586850A2314D6B1003FEC6A /* MockTokenProvider.swift */; };
A586850D23157A8D003FEC6A /* MockURLSessionUploadTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = A586850C23157A8D003FEC6A /* MockURLSessionUploadTask.swift */; };
A5F46C4823186E4C009AB43E /* MockDateFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F46C4723186E4C009AB43E /* MockDateFactory.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
607FACE51AFB9204008FA782 /* IomtFhirClient_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IomtFhirClient_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A52BB53E2346970200650972 /* IomtFhirClientTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IomtFhirClientTests.swift; sourceTree = "<group>"; };
A52BB540234697B400650972 /* IomtFhirClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IomtFhirClient.swift; sourceTree = "<group>"; };
A52BB542234697F500650972 /* IomtFhirClientError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IomtFhirClientError.swift; sourceTree = "<group>"; };
A52BB5442346986200650972 /* HttpIomtFhirClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpIomtFhirClient.swift; sourceTree = "<group>"; };
A55FD906231059AC003E6220 /* MockDataSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDataSender.swift; sourceTree = "<group>"; };
A55FD9082310823A003E6220 /* MockError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockError.swift; sourceTree = "<group>"; };
A55FD90A23108C68003E6220 /* UrlExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlExtensionsTests.swift; sourceTree = "<group>"; };
A57A6771234650C9006BFEEE /* ConnectionManagerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionManagerProtocol.swift; sourceTree = "<group>"; };
A57A6772234650C9006BFEEE /* EventData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventData.swift; sourceTree = "<group>"; };
A57A6773234650C9006BFEEE /* EventDataSenderProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventDataSenderProtocol.swift; sourceTree = "<group>"; };
A57A6774234650C9006BFEEE /* EventDataSender.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventDataSender.swift; sourceTree = "<group>"; };
A57A6776234650C9006BFEEE /* ConnectionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionManager.swift; sourceTree = "<group>"; };
A57A6778234650C9006BFEEE /* UrlExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UrlExtensions.swift; sourceTree = "<group>"; };
A57A677A234650C9006BFEEE /* TransportType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransportType.swift; sourceTree = "<group>"; };
A57A677B234650C9006BFEEE /* TokenProviderProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenProviderProtocol.swift; sourceTree = "<group>"; };
A57A677C234650C9006BFEEE /* DateFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateFactory.swift; sourceTree = "<group>"; };
A57A677D234650C9006BFEEE /* EventHubsTokenError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventHubsTokenError.swift; sourceTree = "<group>"; };
A57A677E234650C9006BFEEE /* SecurityToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecurityToken.swift; sourceTree = "<group>"; };
A57A6780234650C9006BFEEE /* EventHubsMessageError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventHubsMessageError.swift; sourceTree = "<group>"; };
A57A6781234650C9006BFEEE /* EventHubsConnectionStringBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventHubsConnectionStringBuilder.swift; sourceTree = "<group>"; };
A57A6782234650C9006BFEEE /* SharedAccessSignatureTokenProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedAccessSignatureTokenProvider.swift; sourceTree = "<group>"; };
A57A6783234650C9006BFEEE /* DateFactoryProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateFactoryProtocol.swift; sourceTree = "<group>"; };
A57A6784234650C9006BFEEE /* TokenScope.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenScope.swift; sourceTree = "<group>"; };
A57A6785234650C9006BFEEE /* SharedAccessSignatureToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedAccessSignatureToken.swift; sourceTree = "<group>"; };
A57A6787234650C9006BFEEE /* HttpEventDataSender.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpEventDataSender.swift; sourceTree = "<group>"; };
A57A6788234650C9006BFEEE /* HttpMessageConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpMessageConverter.swift; sourceTree = "<group>"; };
A57A678A234650C9006BFEEE /* HttpMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpMessage.swift; sourceTree = "<group>"; };
A57A678B234650C9006BFEEE /* HttpEventHubsConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpEventHubsConnection.swift; sourceTree = "<group>"; };
A57A678C234650C9006BFEEE /* EventHubsConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventHubsConnection.swift; sourceTree = "<group>"; };
A58684F823148E67003FEC6A /* HttpEventDataSenderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpEventDataSenderTests.swift; sourceTree = "<group>"; };
A58684FA23148E8A003FEC6A /* HttpEventHubsConnectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpEventHubsConnectionTests.swift; sourceTree = "<group>"; };
A58684FC23148EA5003FEC6A /* HttpMessageConverterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpMessageConverterTests.swift; sourceTree = "<group>"; };
A58684FE23148EBE003FEC6A /* EventHubsConnectionStringBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventHubsConnectionStringBuilderTests.swift; sourceTree = "<group>"; };
A586850023148ED9003FEC6A /* SharedAccessSignatureTokenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedAccessSignatureTokenTests.swift; sourceTree = "<group>"; };
A586850223148EF0003FEC6A /* SharedAccessSignatureTokenProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedAccessSignatureTokenProviderTests.swift; sourceTree = "<group>"; };
A58685042314947D003FEC6A /* MockConnectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockConnectionManager.swift; sourceTree = "<group>"; };
A58685062314B59D003FEC6A /* MockHttpEventHubsConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockHttpEventHubsConnection.swift; sourceTree = "<group>"; };
A58685082314D4E0003FEC6A /* MockURLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLSession.swift; sourceTree = "<group>"; };
A586850A2314D6B1003FEC6A /* MockTokenProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTokenProvider.swift; sourceTree = "<group>"; };
A586850C23157A8D003FEC6A /* MockURLSessionUploadTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLSessionUploadTask.swift; sourceTree = "<group>"; };
A5F46C4723186E4C009AB43E /* MockDateFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDateFactory.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
607FACE21AFB9204008FA782 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A57A67A823465159006BFEEE /* Quick in Frameworks */,
A57A67AB2346518A006BFEEE /* Nimble in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
607FACC71AFB9204008FA782 = {
isa = PBXGroup;
children = (
607FACE81AFB9204008FA782 /* Tests */,
A57A6770234650C9006BFEEE /* Sources */,
607FACD11AFB9204008FA782 /* Products */,
);
sourceTree = "<group>";
};
607FACD11AFB9204008FA782 /* Products */ = {
isa = PBXGroup;
children = (
607FACE51AFB9204008FA782 /* IomtFhirClient_Tests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
607FACE81AFB9204008FA782 /* Tests */ = {
isa = PBXGroup;
children = (
A55FD90523105992003E6220 /* Mocks */,
A52BB53E2346970200650972 /* IomtFhirClientTests.swift */,
A55FD90A23108C68003E6220 /* UrlExtensionsTests.swift */,
A58684F823148E67003FEC6A /* HttpEventDataSenderTests.swift */,
A58684FA23148E8A003FEC6A /* HttpEventHubsConnectionTests.swift */,
A58684FC23148EA5003FEC6A /* HttpMessageConverterTests.swift */,
A58684FE23148EBE003FEC6A /* EventHubsConnectionStringBuilderTests.swift */,
A586850023148ED9003FEC6A /* SharedAccessSignatureTokenTests.swift */,
A586850223148EF0003FEC6A /* SharedAccessSignatureTokenProviderTests.swift */,
607FACE91AFB9204008FA782 /* Supporting Files */,
);
path = Tests;
sourceTree = "<group>";
};
607FACE91AFB9204008FA782 /* Supporting Files */ = {
isa = PBXGroup;
children = (
607FACEA1AFB9204008FA782 /* Info.plist */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
A55FD90523105992003E6220 /* Mocks */ = {
isa = PBXGroup;
children = (
A55FD906231059AC003E6220 /* MockDataSender.swift */,
A55FD9082310823A003E6220 /* MockError.swift */,
A58685042314947D003FEC6A /* MockConnectionManager.swift */,
A58685062314B59D003FEC6A /* MockHttpEventHubsConnection.swift */,
A58685082314D4E0003FEC6A /* MockURLSession.swift */,
A586850A2314D6B1003FEC6A /* MockTokenProvider.swift */,
A586850C23157A8D003FEC6A /* MockURLSessionUploadTask.swift */,
A5F46C4723186E4C009AB43E /* MockDateFactory.swift */,
);
path = Mocks;
sourceTree = "<group>";
};
A57A6770234650C9006BFEEE /* Sources */ = {
isa = PBXGroup;
children = (
A52BB540234697B400650972 /* IomtFhirClient.swift */,
A57A6771234650C9006BFEEE /* ConnectionManagerProtocol.swift */,
A57A6776234650C9006BFEEE /* ConnectionManager.swift */,
A57A6772234650C9006BFEEE /* EventData.swift */,
A57A6773234650C9006BFEEE /* EventDataSenderProtocol.swift */,
A57A6774234650C9006BFEEE /* EventDataSender.swift */,
A57A6777234650C9006BFEEE /* Extensions */,
A57A6779234650C9006BFEEE /* Primitives */,
A57A6786234650C9006BFEEE /* Http */,
A57A678C234650C9006BFEEE /* EventHubsConnection.swift */,
);
path = Sources;
sourceTree = "<group>";
};
A57A6777234650C9006BFEEE /* Extensions */ = {
isa = PBXGroup;
children = (
A57A6778234650C9006BFEEE /* UrlExtensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
A57A6779234650C9006BFEEE /* Primitives */ = {
isa = PBXGroup;
children = (
A52BB542234697F500650972 /* IomtFhirClientError.swift */,
A57A677D234650C9006BFEEE /* EventHubsTokenError.swift */,
A57A6780234650C9006BFEEE /* EventHubsMessageError.swift */,
A57A677A234650C9006BFEEE /* TransportType.swift */,
A57A677B234650C9006BFEEE /* TokenProviderProtocol.swift */,
A57A677C234650C9006BFEEE /* DateFactory.swift */,
A57A677E234650C9006BFEEE /* SecurityToken.swift */,
A57A6781234650C9006BFEEE /* EventHubsConnectionStringBuilder.swift */,
A57A6782234650C9006BFEEE /* SharedAccessSignatureTokenProvider.swift */,
A57A6783234650C9006BFEEE /* DateFactoryProtocol.swift */,
A57A6784234650C9006BFEEE /* TokenScope.swift */,
A57A6785234650C9006BFEEE /* SharedAccessSignatureToken.swift */,
);
path = Primitives;
sourceTree = "<group>";
};
A57A6786234650C9006BFEEE /* Http */ = {
isa = PBXGroup;
children = (
A57A6787234650C9006BFEEE /* HttpEventDataSender.swift */,
A57A678B234650C9006BFEEE /* HttpEventHubsConnection.swift */,
A52BB5442346986200650972 /* HttpIomtFhirClient.swift */,
A57A678A234650C9006BFEEE /* HttpMessage.swift */,
A57A6788234650C9006BFEEE /* HttpMessageConverter.swift */,
);
path = Http;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
607FACE41AFB9204008FA782 /* IomtFhirClient_Tests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "IomtFhirClient_Tests" */;
buildPhases = (
607FACE11AFB9204008FA782 /* Sources */,
607FACE21AFB9204008FA782 /* Frameworks */,
607FACE31AFB9204008FA782 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = IomtFhirClient_Tests;
packageProductDependencies = (
A57A67A723465159006BFEEE /* Quick */,
A57A67AA2346518A006BFEEE /* Nimble */,
);
productName = Tests;
productReference = 607FACE51AFB9204008FA782 /* IomtFhirClient_Tests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
607FACC81AFB9204008FA782 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0830;
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = CocoaPods;
TargetAttributes = {
607FACE41AFB9204008FA782 = {
CreatedOnToolsVersion = 6.3.1;
LastSwiftMigration = 1020;
};
};
};
buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "IomtFhirClient" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 607FACC71AFB9204008FA782;
packageReferences = (
A57A67A623465159006BFEEE /* XCRemoteSwiftPackageReference "Quick" */,
A57A67A92346518A006BFEEE /* XCRemoteSwiftPackageReference "Nimble" */,
);
productRefGroup = 607FACD11AFB9204008FA782 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
607FACE41AFB9204008FA782 /* IomtFhirClient_Tests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
607FACE31AFB9204008FA782 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
607FACE11AFB9204008FA782 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A57A6797234650C9006BFEEE /* EventHubsTokenError.swift in Sources */,
A5F46C4823186E4C009AB43E /* MockDateFactory.swift in Sources */,
A55FD907231059AC003E6220 /* MockDataSender.swift in Sources */,
A52BB53F2346970200650972 /* IomtFhirClientTests.swift in Sources */,
A57A678E234650C9006BFEEE /* EventData.swift in Sources */,
A55FD90B23108C68003E6220 /* UrlExtensionsTests.swift in Sources */,
A58685052314947D003FEC6A /* MockConnectionManager.swift in Sources */,
A57A6795234650C9006BFEEE /* TokenProviderProtocol.swift in Sources */,
A52BB5452346986200650972 /* HttpIomtFhirClient.swift in Sources */,
A57A6790234650C9006BFEEE /* EventDataSender.swift in Sources */,
A52BB543234697F500650972 /* IomtFhirClientError.swift in Sources */,
A57A679C234650C9006BFEEE /* SharedAccessSignatureTokenProvider.swift in Sources */,
A58684FB23148E8A003FEC6A /* HttpEventHubsConnectionTests.swift in Sources */,
A57A67A4234650C9006BFEEE /* HttpEventHubsConnection.swift in Sources */,
A57A67A3234650C9006BFEEE /* HttpMessage.swift in Sources */,
A58684FF23148EBE003FEC6A /* EventHubsConnectionStringBuilderTests.swift in Sources */,
A57A67A0234650C9006BFEEE /* HttpEventDataSender.swift in Sources */,
A57A679B234650C9006BFEEE /* EventHubsConnectionStringBuilder.swift in Sources */,
A57A6792234650C9006BFEEE /* ConnectionManager.swift in Sources */,
A57A6793234650C9006BFEEE /* UrlExtensions.swift in Sources */,
A57A679E234650C9006BFEEE /* TokenScope.swift in Sources */,
A586850323148EF0003FEC6A /* SharedAccessSignatureTokenProviderTests.swift in Sources */,
A57A679D234650C9006BFEEE /* DateFactoryProtocol.swift in Sources */,
A586850D23157A8D003FEC6A /* MockURLSessionUploadTask.swift in Sources */,
A57A6798234650C9006BFEEE /* SecurityToken.swift in Sources */,
A57A67A1234650C9006BFEEE /* HttpMessageConverter.swift in Sources */,
A58684FD23148EA5003FEC6A /* HttpMessageConverterTests.swift in Sources */,
A586850123148ED9003FEC6A /* SharedAccessSignatureTokenTests.swift in Sources */,
A586850B2314D6B1003FEC6A /* MockTokenProvider.swift in Sources */,
A55FD9092310823A003E6220 /* MockError.swift in Sources */,
A57A679F234650C9006BFEEE /* SharedAccessSignatureToken.swift in Sources */,
A57A6796234650C9006BFEEE /* DateFactory.swift in Sources */,
A58684F923148E67003FEC6A /* HttpEventDataSenderTests.swift in Sources */,
A52BB541234697B400650972 /* IomtFhirClient.swift in Sources */,
A58685072314B59D003FEC6A /* MockHttpEventHubsConnection.swift in Sources */,
A57A678F234650C9006BFEEE /* EventDataSenderProtocol.swift in Sources */,
A57A6794234650C9006BFEEE /* TransportType.swift in Sources */,
A57A678D234650C9006BFEEE /* ConnectionManagerProtocol.swift in Sources */,
A57A67A5234650C9006BFEEE /* EventHubsConnection.swift in Sources */,
A58685092314D4E0003FEC6A /* MockURLSession.swift in Sources */,
A57A679A234650C9006BFEEE /* EventHubsMessageError.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
607FACED1AFB9204008FA782 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
607FACEE1AFB9204008FA782 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
607FACF31AFB9204008FA782 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = Tests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.microsoft.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
607FACF41AFB9204008FA782 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = Tests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.microsoft.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "IomtFhirClient" */ = {
isa = XCConfigurationList;
buildConfigurations = (
607FACED1AFB9204008FA782 /* Debug */,
607FACEE1AFB9204008FA782 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "IomtFhirClient_Tests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
607FACF31AFB9204008FA782 /* Debug */,
607FACF41AFB9204008FA782 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
A57A67A623465159006BFEEE /* XCRemoteSwiftPackageReference "Quick" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Quick/Quick";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.2.0;
};
};
A57A67A92346518A006BFEEE /* XCRemoteSwiftPackageReference "Nimble" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Quick/Nimble";
requirement = {
kind = exactVersion;
version = 8.0.2;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
A57A67A723465159006BFEEE /* Quick */ = {
isa = XCSwiftPackageProductDependency;
package = A57A67A623465159006BFEEE /* XCRemoteSwiftPackageReference "Quick" */;
productName = Quick;
};
A57A67AA2346518A006BFEEE /* Nimble */ = {
isa = XCSwiftPackageProductDependency;
package = A57A67A92346518A006BFEEE /* XCRemoteSwiftPackageReference "Nimble" */;
productName = Nimble;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 607FACC81AFB9204008FA782 /* Project object */;
}

7
IomtFhirClient.xcodeproj/project.xcworkspace/contents.xcworkspacedata сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:AzureEventHubs.xcodeproj">
</FileRef>
</Workspace>

7
IomtFhirClient.xcworkspace/contents.xcworkspacedata сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:IomtFhirClient.xcodeproj">
</FileRef>
</Workspace>

Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

Просмотреть файл

@ -0,0 +1,25 @@
{
"object": {
"pins": [
{
"package": "Nimble",
"repositoryURL": "https://github.com/Quick/Nimble",
"state": {
"branch": null,
"revision": "f8657642dfdec9973efc79cc68bcef43a653a2bc",
"version": "8.0.2"
}
},
{
"package": "Quick",
"repositoryURL": "https://github.com/Quick/Quick",
"state": {
"branch": null,
"revision": "33682c2f6230c60614861dfc61df267e11a1602f",
"version": "2.2.0"
}
}
]
},
"version": 1
}

Просмотреть файл

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "607FACE41AFB9204008FA782"
BuildableName = "IomtFhirClient_Tests.xctest"
BlueprintName = "IomtFhirClient_Tests"
ReferencedContainer = "container:IomtFhirClient.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "607FACE41AFB9204008FA782"
BuildableName = "AzureEventHubs_Tests.xctest"
BlueprintName = "AzureEventHubs_Tests"
ReferencedContainer = "container:AzureEventHubs.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "607FACE41AFB9204008FA782"
BuildableName = "IomtFhirClient_Tests.xctest"
BlueprintName = "IomtFhirClient_Tests"
ReferencedContainer = "container:IomtFhirClient.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "607FACE41AFB9204008FA782"
BuildableName = "IomtFhirClient_Tests.xctest"
BlueprintName = "IomtFhirClient_Tests"
ReferencedContainer = "container:IomtFhirClient.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

21
LICENSE Normal file
Просмотреть файл

@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

25
Package.swift Normal file
Просмотреть файл

@ -0,0 +1,25 @@
// swift-tools-version:5.0
// Package.swift
// AzureEventHubs
//
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import PackageDescription
let package = Package(
name: "IomtFhirClient",
platforms: [
.iOS(.v11)
],
products: [
.library(
name: "IomtFhirClient",
targets: ["IomtFhirClient"]),
],
targets: [
.target(
name: "IomtFhirClient",
path: "Sources"),
]
)

Просмотреть файл

@ -0,0 +1,29 @@
# Xcode
# Build, test, and archive an Xcode workspace on macOS.
# Add steps that install certificates, test, sign, and distribute an app, save build artifacts, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/xcode
trigger: none
schedules:
- cron: "0 12 * * *"
displayName: Daily build
branches:
include:
- master
pool:
vmImage: 'macos-latest'
steps:
- task: Xcode@5
inputs:
actions: 'test'
configuration: 'Debug'
sdk: 'iphoneos'
xcWorkspacePath: '**/IomtFhirClient.xcworkspace'
scheme: 'IomtFhirClient_Tests'
xcodeVersion: '11'
packageApp: false
destinationPlatformOption: 'iOS'
destinationSimulators: 'iPhone 8'

Просмотреть файл

@ -0,0 +1,22 @@
# Xcode
# Build, test, and archive an Xcode workspace on macOS.
# Add steps that install certificates, test, sign, and distribute an app, save build artifacts, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/xcode
trigger: none
pool:
vmImage: 'macos-latest'
steps:
- task: Xcode@5
inputs:
actions: 'test'
configuration: 'Debug'
sdk: 'iphoneos'
xcWorkspacePath: '**/IomtFhirClient.xcworkspace'
scheme: 'IomtFhirClient_Tests'
xcodeVersion: '11'
packageApp: false
destinationPlatformOption: 'iOS'
destinationSimulators: 'iPhone 8'

69
README.md Normal file
Просмотреть файл

@ -0,0 +1,69 @@
# IomtFhirClient Swift Library
[![Build Status](https://microsofthealth.visualstudio.com/Health/_apis/build/status/POET/IomtFhirClient_Daily?branchName=master)](https://microsofthealth.visualstudio.com/Health/_build/latest?definitionId=436&branchName=master)
The IomtFhirClient Swift library simplifies sending IoMT (Internet of Medical Things) data to an [IoMT FHIR Connector for Azure](https://github.com/microsoft/iomt-fhir) endpoint for persistance in a FHIR® server.
## Basic Usage
### Instantiate an IomtFhirClient
An IomtFhirClient can be instantiated using a connection string.
```swift
let iomtFhirClient = try IomtFhirClient.CreateFromConnectionString(connectionString: "YOUR_CONNECTION_STRING")
```
### Create EventData
In the example below, a simple json payload is used to create an EventData object.
```swift
let json = "{\"eventPayload\":\"payload data\"}"
let payload = json.data(using: .utf8)
let eventData = EventData(data: payload)
```
### Send the data to the Azure Event Hub
The IomtFhirClient has methods for sending single EventHub objects or collections.
```swift
do {
try iomtFhirClient.send(eventData: eventData) { (success, error) in
if (success && error == nil) {
// The event was send successfully
} else {
// Handle any errors
}
}
} catch {
// Handle any errors
}
```
## 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 https://cla.opensource.microsoft.com.
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., status check, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
There are many other ways to contribute to the IomtFhirClient Project.
* [Submit bugs](https://github.com/microsoft/iomt-fhir-client/issues) and help us verify fixes as they are checked in.
* Review the [source code changes](https://github.com/microsoft/iomt-fhir-client/pulls).
* [Contribute bug fixes](CONTRIBUTING.md).
See [Contributing to IomtFhirClient](CONTRIBUTING.md) for more information.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
FHIR® is the registered trademark of HL7 and is used with the permission of HL7. Use of the FHIR trademark does not constitute endorsement of this product by HL7.

41
SECURITY.md Normal file
Просмотреть файл

@ -0,0 +1,41 @@
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.3 BLOCK -->
## Security
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below.
## Reporting Security Issues
**Please do not report security vulnerabilities through public GitHub issues.**
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
## Preferred Languages
We prefer all communications to be in English.
## Policy
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
<!-- END MICROSOFT SECURITY.MD BLOCK -->

Просмотреть файл

@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
internal class ConnectionManager : ConnectionManagerProtocol
{
private var connectionMap: [String : EventHubsConnection] = [:]
private let lock = NSObject()
internal func getOrCreate<T: EventHubsConnection>(iomtFhirClient: IomtFhirClient, partitionId: String?) throws -> T
{
objc_sync_enter(lock)
defer
{
objc_sync_exit(lock)
}
let key = String(describing: T.self)
if let manager = connectionMap[key],
manager is T
{
return manager as! T
}
let manager = try T.init(iomtFhirClient: iomtFhirClient, partitionId: partitionId)
connectionMap[key] = manager
return manager;
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public protocol ConnectionManagerProtocol
{
func getOrCreate<T: EventHubsConnection>(iomtFhirClient: IomtFhirClient, partitionId: String?) throws -> T
}

16
Sources/EventData.swift Normal file
Просмотреть файл

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public class EventData
{
public var properties: [String : Any] = [:]
public var data: Data
public init(data: Data)
{
self.data = data
}
}

Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
internal class EventDataSender : EventDataSenderProtocol
{
internal let iomtFhirClient: IomtFhirClient
internal let partitionId: String?
internal init(iomtFhirClient: IomtFhirClient, partitionId: String? = nil)
{
self.iomtFhirClient = iomtFhirClient
self.partitionId = partitionId
}
internal func send(eventDatas: [EventData], completion: @escaping (Bool, Error?) -> Void) throws
{
try onSend(eventDatas: eventDatas, completion: completion)
}
internal func onSend(eventDatas: [EventData], completion: @escaping (Bool, Error?) -> Void) throws
{
preconditionFailure("Method mus be overridden by child class.")
}
internal static func validateEvents(eventDatas: [EventData]) throws -> Int
{
let count = eventDatas.count
if count == 0
{
throw IomtFhirClientError.eventDataEmpty
}
return count
}
}

Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public protocol EventDataSenderProtocol
{
func send(eventDatas: [EventData], completion: @escaping (Bool, Error?) -> Void) throws
func onSend(eventDatas: [EventData], completion: @escaping (Bool, Error?) -> Void) throws
static func validateEvents(eventDatas: [EventData]) throws -> Int
}

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public class EventHubsConnection
{
internal let iomtFhirClient: IomtFhirClient
internal let partitionId: String?
internal required init(iomtFhirClient: IomtFhirClient, partitionId: String?) throws
{
self.iomtFhirClient = iomtFhirClient
self.partitionId = partitionId
}
}

Просмотреть файл

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
internal extension URL
{
func appliesToUriString(tokenScope: TokenScope, ensureTrailingSlash: Bool) -> String?
{
var uri = ""
if let host = self.host
{
uri.append(host)
}
if tokenScope == .entity
{
if !self.path.starts(with: "/")
{
uri.append("/")
}
uri.append(self.path)
}
if ensureTrailingSlash,
!uri.hasSuffix("/")
{
uri.append("/")
}
return uri
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
internal class HttpEventDataSender : EventDataSender
{
override internal func onSend(eventDatas: [EventData], completion: @escaping (Bool, Error?) -> Void) throws
{
// Ensure the data is valid.
let _ = try EventDataSender.validateEvents(eventDatas: eventDatas)
// Convert to the event data to an http message.
let message = try HttpMessageConverter.EventDatasToHttpMessage(eventDatas: eventDatas, partitionId: partitionId)
// Get or create the connection and send the events.
let connection: HttpEventHubsConnection = try iomtFhirClient.connectionManager.getOrCreate(iomtFhirClient: iomtFhirClient, partitionId: partitionId)
try connection.send(httpMessage: message, completion: { (success, error) in completion(success, error) })
}
}

Просмотреть файл

@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
internal class HttpEventHubsConnection : EventHubsConnection
{
internal var session = URLSession.shared
private let httpMethod = "POST"
private let httpHeaderFieldHost = "Host"
private let httpHeaderFieldContentType = "Content-Type"
private let httpHeaderFieldAuthorization = "Authorization"
private let endpoint: URL
internal required init(iomtFhirClient: IomtFhirClient, partitionId: String?) throws
{
guard let endpoint = iomtFhirClient.connectionStringBuilder.endpoint else
{
throw IomtFhirClientError.invalidConnectionString(reason: "A valid URL could not be created using the provided connection string.")
}
self.endpoint = endpoint
try super.init(iomtFhirClient: iomtFhirClient, partitionId: partitionId)
}
internal func send(httpMessage: HttpMessage, completion: ((Bool, Error?) -> Void)?) throws
{
let request = try generateRequest(httpMessage: httpMessage)
let task = session.uploadTask(with: request, from: httpMessage.httpBody) { (data, response, error) in
completion?(self.isSuccessful(response: response, error: error), error)
}
task.resume()
}
private func generateRequest(httpMessage: HttpMessage) throws -> URLRequest
{
var request = URLRequest(url: endpoint)
request.httpMethod = httpMethod
request.addValue(httpMessage.contentType, forHTTPHeaderField: httpHeaderFieldContentType)
if let host = endpoint.host
{
request.addValue(host, forHTTPHeaderField: httpHeaderFieldHost)
}
if let token = try iomtFhirClient.tokenProvider?.getToken(appliesTo: endpoint, timeToLive: nil)
{
request.addValue(token.tokenValue, forHTTPHeaderField: httpHeaderFieldAuthorization)
}
return request
}
private func isSuccessful(response: URLResponse?, error: Error?) -> Bool
{
guard error == nil else
{
return false
}
if let httpResponse = response as? HTTPURLResponse
{
return httpResponse.statusCode < 300
}
return false
}
}

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public class HttpIomtFhirClient : IomtFhirClient
{
/// An specific implemetation of the data sender can be provided for testing.
public var eventDataSender: EventDataSenderProtocol?
internal convenience init(connectionStringBuilder: EventHubsConnectionStringBuilder) throws
{
var tokenProvider: SharedAccessSignatureTokenProvider
// Create the token provider
if let sharedAccessSignature = connectionStringBuilder.sharedAccessSignature
{
tokenProvider = try SharedAccessSignatureTokenProvider(sharedAccessSignature: sharedAccessSignature)
}
else
{
tokenProvider = SharedAccessSignatureTokenProvider(keyName: connectionStringBuilder.sasKeyName!, sharedAccessKey: connectionStringBuilder.sasKey!)
}
try self.init(connectionStringBuilder: connectionStringBuilder, tokenProvider: tokenProvider);
}
override internal func onCreateEventSender(partitionId: String? = nil) -> EventDataSenderProtocol
{
if eventDataSender != nil
{
return eventDataSender!
}
return HttpEventDataSender(iomtFhirClient: self, partitionId: partitionId)
}
}

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
internal class HttpMessage
{
internal let httpBody: Data
internal let contentType: String
internal init(httpBody: Data, contentType: String)
{
self.httpBody = httpBody
self.contentType = contentType
}
}

Просмотреть файл

@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
internal class HttpMessageConverter
{
private static let bodyKey = "Body"
private static let userPropertiesKey = "UserProperties"
private static let brokerPropertiesKey = "BrokerProperties"
private static let serviceBusBatchContentType = "application/vnd.microsoft.servicebus.json"
internal static func EventDatasToHttpMessage(eventDatas: [EventData], partitionId: String?) throws -> HttpMessage
{
guard eventDatas.count > 0 else
{
throw EventHubsMessageError.noEventData
}
var payloadArray: [[String : Any]] = []
for eventdata in eventDatas
{
if let dataString = String(data: eventdata.data, encoding: .utf8),
dataString.count > 0
{
payloadArray.append([bodyKey : dataString])
// Add user provided properties.
if eventdata.properties.count > 0
{
payloadArray.append([userPropertiesKey : eventdata.properties])
}
}
else
{
throw EventHubsMessageError.serializationError(reason: "One or more event data could not be serialized.")
}
}
if (JSONSerialization.isValidJSONObject(payloadArray))
{
if let jsonData = try? JSONSerialization.data(withJSONObject: payloadArray, options: [])
{
return HttpMessage(httpBody: jsonData, contentType: serviceBusBatchContentType)
}
}
throw EventHubsMessageError.serializationError(reason: "The event data could not be serialized into valid JSON.")
}
}

Просмотреть файл

@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public class IomtFhirClient
{
public let eventHubName: String?
public lazy var connectionManager: ConnectionManagerProtocol = { return ConnectionManager() }()
public var tokenProvider: TokenProviderProtocol?
internal let connectionStringBuilder: EventHubsConnectionStringBuilder
public lazy var sender: EventDataSenderProtocol = { return onCreateEventSender() }()
/// Creates a new instance of the IoMT FHIR Client using the specified connection string. You can populate the EntityPath property with the name of the Event Hub.
///
/// - Parameter connectionString: The IoMT FHIR Client connection string (e.g. Endpoint=sb://{yournamespace}.servicebus.windows.net/;SharedAccessKeyName={policyname};SharedAccessKey={key};EntityPath={eventhubname}).
/// - Parameter transportType: The connection transport type (currently only HTTPS is supported).
/// - Returns: A new instance of IomtFhirClient configured for the given connection and transort type.
/** - Throws: Errors thrown by CreateFromConnectionString
'InvalidConnectionString' The connection string is not formatted correctly or is missing a required value.
'UnsupportedTransportType' The TransportType is not supported.
*/
public static func CreateFromConnectionString(connectionString: String) throws -> IomtFhirClient
{
// Instantiate a new connection string builder.
let connectionStringBuilder = try EventHubsConnectionStringBuilder(connectionString: connectionString);
// Ensure the connection string is valid.
try connectionStringBuilder.validate();
if connectionStringBuilder.transportType == .https
{
return try HttpIomtFhirClient(connectionStringBuilder: connectionStringBuilder);
}
// Currently, only HTTPS is supported - throw here if another type was specified.
throw IomtFhirClientError.unsupportedTransportType(transportType: connectionStringBuilder.transportType)
}
internal init(connectionStringBuilder: EventHubsConnectionStringBuilder, tokenProvider: TokenProviderProtocol) throws
{
self.connectionStringBuilder = connectionStringBuilder
self.eventHubName = connectionStringBuilder.entityPath
// Set the token provider if an instance has not already been provided.
if self.tokenProvider == nil
{
self.tokenProvider = tokenProvider
}
}
public func send(eventData: EventData, completion: @escaping (Bool, Error?) -> Void) throws
{
try send(eventDatas: [eventData], partitionKey: nil, completion: completion)
}
public func send(eventDatas: [EventData], completion: @escaping (Bool, Error?) -> Void) throws
{
try send(eventDatas: eventDatas, partitionKey: nil, completion: completion)
}
public func send(eventDatas: [EventData], partitionKey: String?, completion: @escaping (Bool, Error?) -> Void) throws
{
try sender.onSend(eventDatas: eventDatas, completion: completion)
}
internal func onCreateEventSender(partitionId: String? = nil) -> EventDataSenderProtocol
{
preconditionFailure("Method must be overridden by child class.")
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
internal class DateFactory : DateFactoryProtocol
{
internal func now() -> Date
{
return Date()
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public protocol DateFactoryProtocol
{
func now() -> Date
}

Просмотреть файл

@ -0,0 +1,175 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public class EventHubsConnectionStringBuilder
{
private let keyValueSeparator = "="
private let keyValuePairDelimeter = ";"
private let endpointConfigName = "Endpoint"
private let sharedAccessKeyNameConfigName = "SharedAccessKeyName"
private let sharedAccessKeyConfigName = "SharedAccessKey"
private let entityPathConfigName = "EntityPath"
private let operationTimeoutConfigName = "OperationTimeout"
private let transportTypeConfigName = "TransportType"
private let sharedAccessSignatureConfigName = "SharedAccessSignature"
public var endpoint: URL?
public var sasKey: String?
public var sasKeyName: String?
public var sharedAccessSignature: String?
public var entityPath: String?
public var operationTimeout: TimeInterval?
public var transportType = TransportType.https
public init(connectionString: String) throws
{
try parseConnectionString(connectionString: connectionString)
try validate()
}
public init(endpointAddress: URL, entityPath: String, sharedAccessKeyName: String, sharedAccessKey: String, operationTimeout: TimeInterval?) throws
{
self.endpoint = endpointAddress
self.entityPath = entityPath
self.sasKeyName = sharedAccessKeyName
self.operationTimeout = operationTimeout
try validate()
}
public func validate() throws
{
if sharedAccessSignature != nil
{
if sasKeyName != nil,
sasKey != nil
{
throw IomtFhirClientError.invalidConnectionString(reason: "'\(sharedAccessSignatureConfigName)' cannot be specified along with '\(sharedAccessKeyNameConfigName)' or '\(sharedAccessKeyConfigName)'. '\(sharedAccessSignatureConfigName)' alone should be sufficient to Authenticate the request.")
}
}
if sasKeyName != nil && sasKey == nil ||
sasKeyName == nil && sasKey != nil
{
throw IomtFhirClientError.invalidConnectionString(reason: "Please make sure either all or none of the following arguments are defined: '\(sharedAccessKeyNameConfigName), \(sharedAccessKeyConfigName)'")
}
}
private func parseConnectionString(connectionString: String) throws
{
// Split the connection string into key value pairs using ';'.
let keyValuePairs = connectionString.components(separatedBy: keyValuePairDelimeter)
var error: IomtFhirClientError?
// Loop through each key value pair.
for keyValuePair in keyValuePairs
{
// Split the key value pair using the first '=' (The key may contain '=')
if let index = keyValuePair.firstIndex(of: keyValueSeparator.first!)
{
let key = String(keyValuePair.prefix(upTo: index))
let value = String(keyValuePair.suffix(from: keyValuePair.index(index, offsetBy: 1)))
if key.caseInsensitiveCompare(endpointConfigName) == .orderedSame
{
if let url = getEndpoint(uri: value, path: entityPath)
{
endpoint = url
}
else
{
error = IomtFhirClientError.invalidConnectionString(reason: "The '\(endpointConfigName)' parameter is invalid: '\(value)'")
break
}
}
else if key.caseInsensitiveCompare(entityPathConfigName) == .orderedSame
{
entityPath = value
if let uri = endpoint?.absoluteString
{
endpoint = getEndpoint(uri: uri, path: entityPath)
}
}
else if key.caseInsensitiveCompare(sharedAccessKeyNameConfigName) == .orderedSame
{
sasKeyName = value
}
else if key.caseInsensitiveCompare(sharedAccessKeyConfigName) == .orderedSame
{
sasKey = value
}
else if key.caseInsensitiveCompare(sharedAccessSignatureConfigName) == .orderedSame
{
sharedAccessSignature = value
}
else if key.caseInsensitiveCompare(operationTimeoutConfigName) == .orderedSame,
let doubleValue = Double(value)
{
operationTimeout = TimeInterval(doubleValue)
}
else if key.caseInsensitiveCompare(transportTypeConfigName) == .orderedSame
{
if let type = TransportType(rawValue: value.lowercased())
{
transportType = type;
}
else
{
error = IomtFhirClientError.invalidConnectionString(reason: "The specified transport type is invalid '\(value)'.")
break
}
}
else
{
error = IomtFhirClientError.invalidConnectionString(reason: "Illegal connection string parameter name '\(key)'")
break
}
}
else
{
error = IomtFhirClientError.invalidConnectionString(reason: "The connection string is not formatted correctly '\(connectionString)'.")
break
}
}
if error != nil
{
throw error!
}
}
private func getEndpoint(uri: String, path: String?) -> URL?
{
var uri = uri
if uri.hasPrefix("sb://")
{
uri = uri.replacingOccurrences(of: "sb://", with: "https://")
}
if let path = path
{
if !uri.hasSuffix("/"),
!path.hasPrefix("/")
{
uri.append("/")
}
uri.append(path)
uri.append("/messages")
}
return URL(string: uri)
}
}

Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public enum EventHubsMessageError : Error
{
case serializationError(reason: String)
case noEventData
}

Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public enum EventHubsTokenError : Error
{
case invalidTokenString(tokenString: String)
case missingTokenField(tokenField: String)
}

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public enum IomtFhirClientError : Error
{
case invalidConnectionString(reason: String)
case unsupportedTransportType(transportType: TransportType)
case eventDataEmpty
case unableToCreateRequest
}

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
/// Provides information about a security token such as audience, expiry time, and the string token value.
public class SecurityToken
{
/// Gets the actual token.
public let tokenValue: String
/// Gets the expiration time of this token.
public let expiresAt: Date
/// Gets the audience of this token.
public let audience: String
/// Gets the token type.
public let tokenType: String
/// Creates a new instance of the SecurityToken class.
///
/// - Parameters:
/// - tokenString: The token
/// - expiresAt: The expiration time
/// - audience: The audience
/// - tokenType: The type of token
public init(tokenString: String, expiresAt: Date, audience: String, tokenType: String)
{
self.tokenValue = tokenString
self.expiresAt = expiresAt
self.audience = audience
self.tokenType = tokenType
}
}

Просмотреть файл

@ -0,0 +1,121 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public class SharedAccessSignatureToken : SecurityToken
{
public static let sasTokenType = "servicebus.windows.net:sastoken";
internal static let sharedAccessSignature = "SharedAccessSignature";
internal static let signedResource = "sr";
internal static let signature = "sig";
internal static let signedKeyName = "skn";
internal static let signedExpiry = "se";
internal static let MaxKeyNameLength = 256;
internal static let MaxKeyLength = 256;
internal static let sasPairSeparator = "&";
internal static let sasKeyValueSeparator = "=";
public convenience init(tokenString: String) throws
{
let expiresAt = try SharedAccessSignatureToken.getExpirationDateFromToken(tokenString: tokenString)
let audience = try SharedAccessSignatureToken.getAudienceFromToken(tokenString: tokenString)
self.init(tokenString: tokenString, expiresAt: expiresAt, audience: audience, tokenType: SharedAccessSignatureToken.sasTokenType );
}
public static func validate(sharedAccessSignature: String) throws
{
let decodedToken = try decode(tokenString: sharedAccessSignature)
guard decodedToken[signedResource] != nil else
{
throw EventHubsTokenError.missingTokenField(tokenField: signedResource)
}
guard decodedToken[signature] != nil else
{
throw EventHubsTokenError.missingTokenField(tokenField: signature)
}
guard decodedToken[signedExpiry] != nil else
{
throw EventHubsTokenError.missingTokenField(tokenField: signedExpiry)
}
guard decodedToken[signedKeyName] != nil else
{
throw EventHubsTokenError.missingTokenField(tokenField: signedKeyName)
}
}
public static func getAudienceFromToken(tokenString: String) throws -> String
{
return try getTokenValue(tokenString: tokenString, field: signedResource)
}
public static func getExpirationDateFromToken(tokenString: String) throws -> Date
{
let expiresOn = try getTokenValue(tokenString: tokenString, field: signedExpiry)
if let epochTime = TimeInterval(expiresOn)
{
return Date(timeIntervalSince1970: epochTime)
}
throw EventHubsTokenError.missingTokenField(tokenField: signedExpiry)
}
public static func getKeyNameFromToken(tokenString: String) throws -> String
{
return try getTokenValue(tokenString: tokenString, field: signedKeyName)
}
private static func getTokenValue(tokenString: String, field: String) throws -> String
{
let decodedToken = try decode(tokenString: tokenString)
if let value = decodedToken[field]
{
return value
}
throw EventHubsTokenError.missingTokenField(tokenField: field)
}
private static func decode(tokenString: String) throws -> [String : String]
{
let prefix = "\(sharedAccessSignature) "
guard tokenString.hasPrefix(prefix) else
{
throw EventHubsTokenError.invalidTokenString(tokenString: tokenString)
}
let tokenPairs = String(tokenString.dropFirst(prefix.count))
var result: [String : String] = [:]
let sasPairs = tokenPairs.components(separatedBy: sasPairSeparator)
for sasPair in sasPairs
{
if let index = sasPair.firstIndex(of: sasKeyValueSeparator.first!)
{
let key = String(sasPair.prefix(upTo: index))
let value = String(sasPair.suffix(from: sasPair.index(index, offsetBy: 1)))
if let decodedValue = value.removingPercentEncoding
{
result[key] = decodedValue
continue
}
}
throw EventHubsTokenError.invalidTokenString(tokenString: tokenString)
}
return result
}
}

Просмотреть файл

@ -0,0 +1,103 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
import CommonCrypto
internal class SharedAccessSignatureTokenProvider : TokenProviderProtocol
{
public var dateFactory: DateFactoryProtocol = DateFactory()
private var keyName: String
private var sharedAccessKey: String?
private var tokenScope: TokenScope
private var tokenTimeToLive: TimeInterval
private var sharedAccessSignature: String?
internal init(sharedAccessSignature: String) throws
{
// TODO: get scope from token string
tokenScope = .entity
try SharedAccessSignatureToken.validate(sharedAccessSignature: sharedAccessSignature)
self.keyName = try SharedAccessSignatureToken.getKeyNameFromToken(tokenString: sharedAccessSignature)
let expirationDate = try SharedAccessSignatureToken.getExpirationDateFromToken(tokenString: sharedAccessSignature)
self.tokenTimeToLive = expirationDate.timeIntervalSince1970
self.sharedAccessSignature = sharedAccessSignature
}
internal init(keyName: String, sharedAccessKey: String, tokenTimeToLive: TimeInterval? = nil, tokenScope: TokenScope = TokenScope.entity)
{
self.keyName = keyName
self.sharedAccessKey = sharedAccessKey
self.tokenScope = tokenScope
self.tokenTimeToLive = tokenTimeToLive ?? TimeInterval(integerLiteral: 3600) // Default token TTL is 60 minutes
}
internal func getToken(appliesTo: URL, timeToLive: TimeInterval?) throws -> SecurityToken
{
if sharedAccessSignature != nil
{
return try SharedAccessSignatureToken(tokenString: sharedAccessSignature!)
}
if let targetUri = appliesTo.appliesToUriString(tokenScope: tokenScope, ensureTrailingSlash: true)
{
let tokenString = try buildSignature(keyName: keyName, sharedAccessKey: sharedAccessKey!, targetUri: targetUri, timeToLive: timeToLive ?? tokenTimeToLive)
return try SharedAccessSignatureToken(tokenString: tokenString)
}
throw EventHubsTokenError.missingTokenField(tokenField: SharedAccessSignatureToken.signedResource)
}
private func buildSignature(keyName: String, sharedAccessKey: String, targetUri: String, timeToLive:TimeInterval) throws -> String
{
guard let audienceUri = targetUri.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else
{
throw EventHubsTokenError.missingTokenField(tokenField: SharedAccessSignatureToken.signedResource)
}
guard let encodedKeyName = keyName.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else
{
throw EventHubsTokenError.missingTokenField(tokenField: SharedAccessSignatureToken.signedKeyName)
}
let expiresOn = buildExpiresOn(timeToLive: timeToLive)
let data = "\(audienceUri)\n\(expiresOn)".lowercased()
let signature = buildSignature(key: sharedAccessKey, data: data)
guard let encodedSignature = signature.addingPercentEncoding(withAllowedCharacters: .alphanumerics) else
{
throw EventHubsTokenError.missingTokenField(tokenField: SharedAccessSignatureToken.signature)
}
let pairSep = SharedAccessSignatureToken.sasPairSeparator
let valSep = SharedAccessSignatureToken.sasKeyValueSeparator
return "\(SharedAccessSignatureToken.sharedAccessSignature) \(SharedAccessSignatureToken.signedResource)\(valSep)\(audienceUri.lowercased())\(pairSep)\(SharedAccessSignatureToken.signature)\(valSep)\(encodedSignature)\(pairSep)\(SharedAccessSignatureToken.signedExpiry)\(valSep)\(expiresOn)\(pairSep)\(SharedAccessSignatureToken.signedKeyName)\(valSep)\(encodedKeyName)"
}
private func buildExpiresOn(timeToLive: TimeInterval) -> String
{
let now = dateFactory.now()
let expiresOn = now.timeIntervalSince1970 + timeToLive
return String(describing: Int(expiresOn));
}
private func buildSignature(key: String, data: String) -> String
{
let keyString = key.cString(using: .ascii)
let keyLength = Int(key.lengthOfBytes(using: .ascii))
let dataString = data.cString(using: .ascii)
let dataLength = Int(data.lengthOfBytes(using: .ascii))
let resultLength = Int(CC_SHA256_DIGEST_LENGTH)
let result = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: resultLength)
CCHmac(UInt32(kCCHmacAlgSHA256) ,keyString, keyLength, dataString, dataLength, result)
let data = NSData(bytes: result, length: resultLength)
return data.base64EncodedString()
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
/// Provides interface definition of a token provider.
public protocol TokenProviderProtocol
{
/// Gets a SecurityToken
///
/// - Parameter appliesTo: The URI which the access token applies to.
/// - Parameter timeToLive: The duration (from now) the token is valid. Optional - a default TTL will be assigned if a value is not provided.
/// - Returns: A new instance of SecurityToken
/** - Throws: Errors thrown by getToken
'InvalidTokenString' The token string is not formatted correctly.
'MissingTokenField' A required field to construct the token is missing.
*/
func getToken(appliesTo: URL, timeToLive: TimeInterval?) throws -> SecurityToken
}

Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
internal enum TokenScope
{
case namespace
case entity
}

Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public enum TransportType : String
{
case https = "https"
case amqps = "amqps"
}

Просмотреть файл

@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Quick
import Nimble
class EventHubsConnectionStringBuilderSpec: QuickSpec {
override func spec() {
describe("EventHubsConnectionStringBuilder") {
context("init is called") {
context("with a string containing a SharedAccessSignature") {
it("does not throw an error") {
expect { try EventHubsConnectionStringBuilder(connectionString: "SharedAccessSignature=TESTSAS") }.toNot(throwError())
}
context("and a SharedAccessKeyName") {
it("throws the expected error") {
expect { try EventHubsConnectionStringBuilder(connectionString: "SharedAccessSignature=TESTSAS;SharedAccessKeyName=TESTKEYNAME") }.to(throwError(IomtFhirClientError.invalidConnectionString(reason: "Please make sure either all or none of the following arguments are defined: \'SharedAccessKeyName, SharedAccessKey\'")))
}
}
context("and a SharedAccessKey") {
it("throws the expected error") {
expect { try EventHubsConnectionStringBuilder(connectionString: "SharedAccessSignature=TESTSAS;SharedAccessKey=TESTKEY") }.to(throwError(IomtFhirClientError.invalidConnectionString(reason: "Please make sure either all or none of the following arguments are defined: \'SharedAccessKeyName, SharedAccessKey\'")))
}
}
}
context("with a string containing a SharedAccessKeyName") {
it("throws the expected error") {
expect { try EventHubsConnectionStringBuilder(connectionString: "SharedAccessKeyName=TESTKEYNAME") }.to(throwError(IomtFhirClientError.invalidConnectionString(reason: "Please make sure either all or none of the following arguments are defined: \'SharedAccessKeyName, SharedAccessKey\'")))
}
context("and a SharedAccessKey") {
it("does not throw an error") {
expect { try EventHubsConnectionStringBuilder(connectionString: "SharedAccessKeyName=TESTKEYNAME;SharedAccessKey=TESTKEY") }.toNot(throwError())
}
}
}
context("with a string containing an EntityPath") {
let builder = try! EventHubsConnectionStringBuilder(connectionString: "EntityPath=TESTPATH")
it("sets the entityPath property") {
expect(builder.entityPath) == "TESTPATH"
}
}
context("with a string containing an Endpoint") {
context("with a valid url") {
let builder = try! EventHubsConnectionStringBuilder(connectionString: "Endpoint=sb://test.servicebus.windows.net/")
it("sets the endpoint property") {
expect(builder.endpoint?.absoluteString) == "https://test.servicebus.windows.net/"
}
context("and an EntityPath") {
let builder = try! EventHubsConnectionStringBuilder(connectionString: "Endpoint=sb://test.servicebus.windows.net/;EntityPath=TESTPATH")
it("sets the endpoint property") {
expect(builder.endpoint?.absoluteString) == "https://test.servicebus.windows.net/TESTPATH/messages"
}
}
}
context("with an invalid url") {
it("throws with the expected error") {
expect { try EventHubsConnectionStringBuilder(connectionString: "Endpoint=\\") }.to(throwError(IomtFhirClientError.invalidConnectionString(reason: "The \'Endpoint\' parameter is invalid: \'\\\'")))
}
}
}
context("with a string containing an OperationTimeout") {
context("with a numerical value") {
let builder = try! EventHubsConnectionStringBuilder(connectionString: "OperationTimeout=30")
it("sets the operationTimeout property") {
expect(builder.operationTimeout) == 30
}
}
context("with a non-numerical value") {
it("throws with the expected error") {
expect { try EventHubsConnectionStringBuilder(connectionString: "OperationTimeout=Thirty") }.to(throwError(IomtFhirClientError.invalidConnectionString(reason: "Illegal connection string parameter name \'OperationTimeout\'")))
}
}
}
context("with a string containing a TransportType") {
context("https") {
let builder = try! EventHubsConnectionStringBuilder(connectionString: "TransportType=https")
it("sets the transportType property") {
expect(builder.transportType) == .https
}
}
context("amqps") {
let builder = try! EventHubsConnectionStringBuilder(connectionString: "TransportType=amqps")
it("sets the transportType property") {
expect(builder.transportType) == .amqps
}
}
context("unsupported transport type") {
it("throws with the expected error") {
expect { try EventHubsConnectionStringBuilder(connectionString: "TransportType=mqtt") }.to(throwError(IomtFhirClientError.invalidConnectionString(reason: "The specified transport type is invalid \'mqtt\'.")))
}
}
}
context("with an invalid connection string") {
it("throws with the expected error") {
expect { try EventHubsConnectionStringBuilder(connectionString: "INVALID_CONNECTION_STRING") }.to(throwError(IomtFhirClientError.invalidConnectionString(reason: "The connection string is not formatted correctly \'INVALID_CONNECTION_STRING\'.")))
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,192 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
import Quick
import Nimble
class HttpEventDataSenderSpec: QuickSpec {
override func spec() {
describe("HttpEventDataSender") {
context("validateEvents is called") {
context("with an array containing at least one message") {
it("returns the expected count") {
expect(try! HttpEventDataSender.validateEvents(eventDatas: self.eventData(count: 1))) == 1
}
}
context("with an empty array") {
it("throws the expected error") {
expect { try HttpEventDataSender.validateEvents(eventDatas: self.eventData(count: 0)) }.to(throwError(IomtFhirClientError.eventDataEmpty))
}
}
}
context("send is called") {
context("with one message") {
context("the message converter fails to convert the event data") {
let test = testObjects()
let eventData = EventData(data: Data())
it("throws the expected error") {
expect { try test.sender.send(eventDatas: [eventData], completion: { _, _ in }) }.to(throwError(EventHubsMessageError.serializationError(reason: "")))
}
}
context("creating the connection fails") {
let test = testObjects()
test.connectionManager.getOrCreateReturns.append(MockError.getOrCreateThrow)
it("throws the expected error") {
expect { try test.sender.send(eventDatas: self.eventData(count: 1), completion: { _, _ in }) }.to(throwError(MockError.getOrCreateThrow))
}
}
context("connection send fails") {
let test = testObjects()
let connection = try! MockHttpEventHubsConnection(iomtFhirClient: test.client, partitionId: nil)
connection.sendCompletions.append((false, MockError.onSendError))
test.connectionManager.getOrCreateReturns.append(connection)
waitUntil { completed in
try! test.sender.send(eventDatas: self.eventData(count: 1), completion: { (success, error) in
it("completes unsuccessfully") {
expect(success).to(beFalse())
}
it("completes with the expected error") {
expect(error).to(matchError(MockError.onSendError))
}
completed()
})
}
}
context("connection send succeeds") {
let test = testObjects()
let connection = try! MockHttpEventHubsConnection(iomtFhirClient: test.client, partitionId: nil)
connection.sendCompletions.append((true, nil))
test.connectionManager.getOrCreateReturns.append(connection)
waitUntil { completed in
try! test.sender.send(eventDatas: self.eventData(count: 1), completion: { (success, error) in
it("completes successfully") {
expect(success).to(beTrue())
}
it("completes with no error") {
expect(error).to(beNil())
}
it("calls the connection with the expected message") {
let message = connection.sendParams.first
expect(message?.contentType) == "application/vnd.microsoft.servicebus.json"
expect(String(data:message!.httpBody, encoding: .utf8)) == "[{\"Body\":\"{\\\"valid\\\":\\\"json\\\"}\"}]"
}
completed()
})
}
}
}
context("with an empty array") {
let test = testObjects()
it("throws the expected error") {
expect { try test.sender.send(eventDatas: [], completion: { _, _ in }) }.to(throwError(IomtFhirClientError.eventDataEmpty))
}
}
context("with multiple messages") {
context("connection send succeeds") {
let test = testObjects()
let connection = try! MockHttpEventHubsConnection(iomtFhirClient: test.client, partitionId: nil)
connection.sendCompletions.append((true, nil))
test.connectionManager.getOrCreateReturns.append(connection)
waitUntil { completed in
try! test.sender.send(eventDatas: self.eventData(count: 4), completion: { (success, error) in
it("completes successfully") {
expect(success).to(beTrue())
}
it("completes with no error") {
expect(error).to(beNil())
}
it("calls the connection with the expected message") {
let message = connection.sendParams.first
expect(message?.contentType) == "application/vnd.microsoft.servicebus.json"
expect(String(data:message!.httpBody, encoding: .utf8)) == "[{\"Body\":\"{\\\"valid\\\":\\\"json\\\"}\"},{\"Body\":\"{\\\"valid\\\":\\\"json\\\"}\"},{\"Body\":\"{\\\"valid\\\":\\\"json\\\"}\"},{\"Body\":\"{\\\"valid\\\":\\\"json\\\"}\"}]"
}
completed()
})
}
}
}
}
context("onSend is called") {
context("with one message") {
context("the message converter fails to convert the event data") {
let test = testObjects()
let eventData = EventData(data: Data())
it("throws the expected error") {
expect { try test.sender.onSend(eventDatas: [eventData], completion: { _, _ in }) }.to(throwError(EventHubsMessageError.serializationError(reason: "")))
}
}
context("creating the connection fails") {
let test = testObjects()
test.connectionManager.getOrCreateReturns.append(MockError.getOrCreateThrow)
it("throws the expected error") {
expect { try test.sender.onSend(eventDatas: self.eventData(count: 1), completion: { _, _ in }) }.to(throwError(MockError.getOrCreateThrow))
}
}
context("connection send fails") {
let test = testObjects()
let connection = try! MockHttpEventHubsConnection(iomtFhirClient: test.client, partitionId: nil)
connection.sendCompletions.append((false, MockError.onSendError))
test.connectionManager.getOrCreateReturns.append(connection)
waitUntil { completed in
try! test.sender.onSend(eventDatas: self.eventData(count: 1), completion: { (success, error) in
it("completes unsuccessfully") {
expect(success).to(beFalse())
}
it("completes with the expected error") {
expect(error).to(matchError(MockError.onSendError))
}
completed()
})
}
}
context("connection send succeeds") {
let test = testObjects()
let connection = try! MockHttpEventHubsConnection(iomtFhirClient: test.client, partitionId: nil)
connection.sendCompletions.append((true, nil))
test.connectionManager.getOrCreateReturns.append(connection)
waitUntil { completed in
try! test.sender.onSend(eventDatas: self.eventData(count: 1), completion: { (success, error) in
it("completes successfully") {
expect(success).to(beTrue())
}
it("completes with no error") {
expect(error).to(beNil())
}
it("calls the connection with the expected message") {
let message = connection.sendParams.first
expect(message?.contentType) == "application/vnd.microsoft.servicebus.json"
expect(String(data:message!.httpBody, encoding: .utf8)) == "[{\"Body\":\"{\\\"valid\\\":\\\"json\\\"}\"}]"
}
completed()
})
}
}
}
context("with an empty array") {
let test = testObjects()
it("throws the expected error") {
expect { try test.sender.onSend(eventDatas: [], completion: { _, _ in }) }.to(throwError(IomtFhirClientError.eventDataEmpty))
}
}
}
}
}
private func testObjects() -> (connectionManager: MockConnectionManager, client: HttpIomtFhirClient, sender: HttpEventDataSender) {
let mockConnectionManager = MockConnectionManager()
let client = try! IomtFhirClient.CreateFromConnectionString(connectionString: "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=TESTKEYNAME;SharedAccessKey=TESTTOKEN;EntityPath=TESTPATH")
client.connectionManager = mockConnectionManager
let sender = HttpEventDataSender(iomtFhirClient: client)
return (mockConnectionManager, client as! HttpIomtFhirClient, sender)
}
private func eventData(count: Int) -> [EventData] {
var eventData = [EventData]()
for _ in 0..<count {
let data = "{\"valid\":\"json\"}".data(using: .utf8)!
eventData.append(EventData(data: data))
}
return eventData
}
}

Просмотреть файл

@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
import Quick
import Nimble
class HttpEventHubsConnectionSpec: QuickSpec {
override func spec() {
describe("HttpEventHubsConnection") {
context("send is called") {
context("the token provider throws") {
let test = testObjects()
test.tokenProvider.getTokenReturns.append(MockError.getTokenThrows)
it("throws the expected error") {
expect { try test.connection.send(httpMessage: HttpMessage(httpBody: Data(), contentType: "TEST_TYPE"), completion: nil) }.to(throwError(MockError.getTokenThrows))
}
}
context("the upload task fails with an error") {
let test = testObjects()
test.tokenProvider.getTokenReturns.append(SecurityToken(tokenString: "value", expiresAt: Date(), audience: "audience", tokenType: "testToken"))
test.urlSession.uploadTaskCompletions.append((nil, error: MockError.uploadTaskError))
waitUntil { completed in
try! test.connection.send(httpMessage: HttpMessage(httpBody: Data(), contentType: "TEST_TYPE"), completion: { (success, error) in
it("completes unsuccessfully") {
expect(success).to(beFalse())
}
it("completes with the expected error") {
expect(error).to(matchError(MockError.uploadTaskError))
}
completed()
})
}
}
context("the upload task fails with a 500") {
let test = testObjects()
test.tokenProvider.getTokenReturns.append(SecurityToken(tokenString: "value", expiresAt: Date(), audience: "audience", tokenType: "testToken"))
let response = HTTPURLResponse(url: URL(string: "https://test.com")!, statusCode: 500, httpVersion: nil, headerFields: nil)
test.urlSession.uploadTaskCompletions.append((response, error: nil))
waitUntil { completed in
try! test.connection.send(httpMessage: HttpMessage(httpBody: Data(), contentType: "TEST_TYPE"), completion: { (success, error) in
it("completes unsuccessfully") {
expect(success).to(beFalse())
}
it("completes with no error") {
expect(error).to(beNil())
}
completed()
})
}
}
context("the upload task is successful") {
let test = testObjects()
test.tokenProvider.getTokenReturns.append(SecurityToken(tokenString: "tokenString", expiresAt: Date(), audience: "audience", tokenType: "testToken"))
let response = HTTPURLResponse(url: URL(string: "https://test.com")!, statusCode: 201, httpVersion: nil, headerFields: nil)
test.urlSession.uploadTaskCompletions.append((response, error: nil))
let expectedData = Data(base64Encoded: "TEST")!
waitUntil { completed in
try! test.connection.send(httpMessage: HttpMessage(httpBody: expectedData, contentType: "testContentType"), completion: { (success, error) in
it("completes successfully") {
expect(success).to(beTrue())
}
it("completes with no error") {
expect(error).to(beNil())
}
it("calls upload with the expected endpoint"){
expect(test.urlSession.uploadTaskParams.first?.request.url?.absoluteString) == "https://test.servicebus.windows.net/TESTPATH/messages"
}
it("calls upload with the expected method"){
expect(test.urlSession.uploadTaskParams.first?.request.httpMethod) == "POST"
}
it("calls upload with the expected Host header") {
expect(test.urlSession.uploadTaskParams.first?.request.allHTTPHeaderFields?["Host"]) == "test.servicebus.windows.net"
}
it("calls upload with the expected Content-Type header") {
expect(test.urlSession.uploadTaskParams.first?.request.allHTTPHeaderFields?["Content-Type"]) == "testContentType"
}
it("calls upload with the expected Authorization header") {
expect(test.urlSession.uploadTaskParams.first?.request.allHTTPHeaderFields?["Authorization"]) == "tokenString"
}
it("calls upload with the expected http body data") {
expect(test.urlSession.uploadTaskParams.first?.bodyData).to(equal(expectedData))
}
completed()
})
}
}
}
}
}
private func testObjects() -> (client: IomtFhirClient, tokenProvider: MockTokenProvider, urlSession: MockURLSession, connection: HttpEventHubsConnection) {
let client = try! IomtFhirClient.CreateFromConnectionString(connectionString: "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=TESTKEYNAME;SharedAccessKey=TESTTOKEN;EntityPath=TESTPATH")
let tokenProvider = MockTokenProvider()
client.tokenProvider = tokenProvider
let connection = try! HttpEventHubsConnection(iomtFhirClient: client, partitionId: nil)
let urlSession = MockURLSession()
connection.session = urlSession
return (client, tokenProvider, urlSession, connection)
}
}

Просмотреть файл

@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
import Quick
import Nimble
class HttpMessageConverterSpec: QuickSpec {
override func spec() {
describe("HttpMessageConverter") {
context("EventDatasToHttpMessage is called") {
context("with an empty array") {
it("throws the expected error") {
expect { try HttpMessageConverter.EventDatasToHttpMessage(eventDatas: [], partitionId: nil) }.to(throwError(EventHubsMessageError.noEventData))
}
}
context("with an empty event") {
it("throws the expected error") {
expect { return try HttpMessageConverter.EventDatasToHttpMessage(eventDatas: [EventData(data: Data())], partitionId: nil) }.to(throwError(EventHubsMessageError.serializationError(reason: "One or more event data could not be serialized.")))
}
}
context("with an event that contains invalid json") {
let eventData = EventData(data: "{\"valid\":\"json\"}".data(using: .utf8)!)
eventData.properties = ["user" : NSData()]
it("throws the expected error") {
expect { return try HttpMessageConverter.EventDatasToHttpMessage(eventDatas: [eventData], partitionId: nil) }.to(throwError(EventHubsMessageError.serializationError(reason: "The event data could not be serialized into valid JSON.")))
}
}
context("with a single event") {
let message = try! HttpMessageConverter.EventDatasToHttpMessage(eventDatas: [EventData(data: "{\"valid\":\"json\"}".data(using: .utf8)!)], partitionId: nil)
it("creates a message with the expected http body") {
expect(String(data: message.httpBody, encoding: .utf8)) == "[{\"Body\":\"{\\\"valid\\\":\\\"json\\\"}\"}]"
}
it("creates a message with the expected content type") {
expect(message.contentType) == "application/vnd.microsoft.servicebus.json"
}
}
context("with an event that has user properties") {
let eventData = EventData(data: "{\"valid\":\"json\"}".data(using: .utf8)!)
eventData.properties = ["user" : "property"]
let message = try! HttpMessageConverter.EventDatasToHttpMessage(eventDatas: [eventData], partitionId: nil)
it("creates a message with the expected http body") {
expect(String(data: message.httpBody, encoding: .utf8)) == "[{\"Body\":\"{\\\"valid\\\":\\\"json\\\"}\"},{\"UserProperties\":{\"user\":\"property\"}}]"
}
it("creates a message with the expected content type") {
expect(message.contentType) == "application/vnd.microsoft.servicebus.json"
}
}
context("with a multiple events") {
let eventData1 = EventData(data: "{\"one\":\"1\"}".data(using: .utf8)!)
let eventData2 = EventData(data: "{\"two\":\"2\"}".data(using: .utf8)!)
let eventData3 = EventData(data: "{\"three\":\"3\"}".data(using: .utf8)!)
let message = try! HttpMessageConverter.EventDatasToHttpMessage(eventDatas: [eventData1, eventData2, eventData3], partitionId: nil)
it("creates a message with the expected http body") {
expect(String(data: message.httpBody, encoding: .utf8)) == "[{\"Body\":\"{\\\"one\\\":\\\"1\\\"}\"},{\"Body\":\"{\\\"two\\\":\\\"2\\\"}\"},{\"Body\":\"{\\\"three\\\":\\\"3\\\"}\"}]"
}
it("creates a message with the expected content type") {
expect(message.contentType) == "application/vnd.microsoft.servicebus.json"
}
}
context("with a multiple events with user properties") {
let eventData1 = EventData(data: "{\"one\":\"1\"}".data(using: .utf8)!)
eventData1.properties = ["user1" : "property1"]
let eventData2 = EventData(data: "{\"two\":\"2\"}".data(using: .utf8)!)
eventData2.properties = ["user2" : "property2"]
let eventData3 = EventData(data: "{\"three\":\"3\"}".data(using: .utf8)!)
eventData3.properties = ["user3" : "property3"]
let message = try! HttpMessageConverter.EventDatasToHttpMessage(eventDatas: [eventData1, eventData2, eventData3], partitionId: nil)
it("creates a message with the expected http body") {
expect(String(data: message.httpBody, encoding: .utf8)) == "[{\"Body\":\"{\\\"one\\\":\\\"1\\\"}\"},{\"UserProperties\":{\"user1\":\"property1\"}},{\"Body\":\"{\\\"two\\\":\\\"2\\\"}\"},{\"UserProperties\":{\"user2\":\"property2\"}},{\"Body\":\"{\\\"three\\\":\\\"3\\\"}\"},{\"UserProperties\":{\"user3\":\"property3\"}}]"
}
it("creates a message with the expected content type") {
expect(message.contentType) == "application/vnd.microsoft.servicebus.json"
}
}
}
}
}
}

24
Tests/Info.plist Normal file
Просмотреть файл

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

Просмотреть файл

@ -0,0 +1,146 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
import Quick
import Nimble
class IomtFhirClientSpec: QuickSpec {
override func spec() {
describe("IomtFhirClient") {
context("CreateFromConnectionString is called") {
context("with an invalid connection string") {
it("throws the expected error") {
expect { _ = try IomtFhirClient.CreateFromConnectionString(connectionString: "INVALID_STRING") }.to(throwError(IomtFhirClientError.invalidConnectionString(reason: "The connection string is not formatted correctly \'INVALID_STRING\'.")))
}
}
context("with a connection string using an unsupported transport type") {
it("throws the expected error") {
let connectionString = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=TESTKEYNAME;SharedAccessKey=TESTTOKEN;EntityPath=TESTPATH;TransportType=amqps"
expect { _ = try IomtFhirClient.CreateFromConnectionString(connectionString: connectionString) }.to(throwError(IomtFhirClientError.unsupportedTransportType(transportType: TransportType.amqps)))
}
}
context("with a valid connection string") {
context("using http transport") {
let client = try! IomtFhirClient.CreateFromConnectionString(connectionString: "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=TESTKEYNAME;SharedAccessKey=TESTTOKEN;EntityPath=TESTPATH")
it("creates a client of the expected type") {
expect(client).to(beAKindOf(HttpIomtFhirClient.self))
}
it("creates a token provider of the expected type") {
expect(client.tokenProvider).to(beAKindOf(SharedAccessSignatureTokenProvider.self))
}
}
}
}
context("send event data is called") {
context("the call to the sender is successful") {
let test = testObjects()
test.sender.onSendCompletions.append((nil, true, nil))
let eventData = EventData(data: Data())
waitUntil { completed in
try! test.client.send(eventData: eventData, completion: { (success, error) in
it("completes successfully") {
expect(success).to(beTrue())
}
it("does not return an error") {
expect(error).to(beNil())
}
it("calls the sender with the event data") {
expect(test.sender.onSendParams.count) == 1
expect(test.sender.onSendParams[0][0]).to(be(eventData))
}
completed()
})
}
}
context("the call to the sender fails") {
let test = testObjects()
test.sender.onSendCompletions.append((nil, false, MockError.onSendError))
let eventData = EventData(data: Data())
waitUntil { completed in
try! test.client.send(eventData: eventData, completion: { (success, error) in
it("completes unsuccessfully") {
expect(success).to(beFalse())
}
it("returns the expected error") {
expect(error).to(matchError(MockError.onSendError))
}
completed()
})
}
}
context("the call to the sender throws") {
let test = testObjects()
test.sender.onSendCompletions.append((MockError.onSendThrow, true, nil))
let eventData = EventData(data: Data())
expect { try test.client.send(eventData: eventData, completion: { (success, error) in }) }.to(throwError(MockError.onSendThrow))
}
}
context("send event datas is called") {
context("the call to the sender is successful") {
let test = testObjects()
test.sender.onSendCompletions.append((nil, true, nil))
let eventData1 = EventData(data: Data())
let eventData2 = EventData(data: Data())
let eventData3 = EventData(data: Data())
waitUntil { completed in
try! test.client.send(eventDatas: [eventData1, eventData2, eventData3], completion: { (success, error) in
it("completes successfully") {
expect(success).to(beTrue())
}
it("does not return an error") {
expect(error).to(beNil())
}
it("calls the sender with the event data objects") {
expect(test.sender.onSendParams.count) == 1
expect(test.sender.onSendParams[0].count) == 3
expect(test.sender.onSendParams[0]).to(containElementSatisfying({ (eventData) -> Bool in
for data in [eventData1, eventData2, eventData3] {
if data.data == eventData.data {
return true
}
}
return false
}))
}
completed()
})
}
}
context("the call to the sender fails") {
let test = testObjects()
test.sender.onSendCompletions.append((nil, false, MockError.onSendError))
let eventData1 = EventData(data: Data())
let eventData2 = EventData(data: Data())
let eventData3 = EventData(data: Data())
waitUntil { completed in
try! test.client.send(eventDatas: [eventData1, eventData2, eventData3], completion: { (success, error) in
it("completes unsuccessfully") {
expect(success).to(beFalse())
}
it("returns the expected error") {
expect(error).to(matchError(MockError.onSendError))
}
completed()
})
}
}
context("the call to the sender throws") {
let test = testObjects()
test.sender.onSendCompletions.append((MockError.onSendThrow, true, nil))
let eventData1 = EventData(data: Data())
let eventData2 = EventData(data: Data())
let eventData3 = EventData(data: Data())
expect { try test.client.send(eventDatas: [eventData1, eventData2, eventData3], completion: { (success, error) in }) }.to(throwError(MockError.onSendThrow))
}
}
}
}
private func testObjects() -> (sender: MockDataSender, client: IomtFhirClient) {
let sender = MockDataSender()
let client = try! IomtFhirClient.CreateFromConnectionString(connectionString: "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=TESTKEYNAME;SharedAccessKey=TESTTOKEN;EntityPath=TESTPATH")
client.sender = sender
return (sender, client)
}
}

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public class MockConnectionManager : ConnectionManagerProtocol {
public var getOrCreateParams = [(iomtFhirClient: IomtFhirClient, partitionId: String?)]()
public var getOrCreateReturns = [Any]()
public func getOrCreate<T>(iomtFhirClient: IomtFhirClient, partitionId: String?) throws -> T where T : EventHubsConnection {
getOrCreateParams.append((iomtFhirClient, partitionId))
let ret = getOrCreateReturns.removeFirst()
if let error = ret as? Error {
throw error
}
return ret as! T
}
}

Просмотреть файл

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public class MockDataSender : EventDataSenderProtocol {
public var sendParams = [[EventData]]()
public var sendCompletions = [(throw: Error?, success: Bool, error: Error?)]()
public var onSendParams = [[EventData]]()
public var onSendCompletions = [(throw: Error?, success: Bool, error: Error?)]()
public func send(eventDatas: [EventData], completion: @escaping (Bool, Error?) -> Void) throws {
sendParams.append(eventDatas)
let comp = sendCompletions.removeFirst()
if let error = comp.throw {
throw error
}
completion(comp.success, comp.error)
}
public func onSend(eventDatas: [EventData], completion: @escaping (Bool, Error?) -> Void) throws {
onSendParams.append(eventDatas)
let comp = onSendCompletions.removeFirst()
if let error = comp.throw {
throw error
}
completion(comp.success, comp.error)
}
public static func validateEvents(eventDatas: [EventData]) throws -> Int {
return 1
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public class MockDateFactory : DateFactoryProtocol {
public var nowReturns = [Date]()
public func now() -> Date {
return nowReturns.removeFirst()
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public enum MockError : Error {
case onSendError
case onSendThrow
case getOrCreateThrow
case getTokenThrows
case uploadTaskError
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
internal class MockHttpEventHubsConnection : HttpEventHubsConnection {
public var sendParams = [HttpMessage]()
public var sendCompletions = [(success: Bool, error: Error?)]()
open override func send(httpMessage: HttpMessage, completion: ((Bool, Error?) -> Void)?) throws {
sendParams.append(httpMessage)
let comp = sendCompletions.removeFirst()
completion?(comp.success, comp.error)
}
}

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public class MockTokenProvider : TokenProviderProtocol {
public var getTokenParams = [(appliesTo: URL, timeToLive: TimeInterval?)]()
public var getTokenReturns = [Any]()
public func getToken(appliesTo: URL, timeToLive: TimeInterval?) throws -> SecurityToken {
getTokenParams.append((appliesTo, timeToLive))
let ret = getTokenReturns.removeFirst()
if let error = ret as? Error {
throw error
}
return ret as! SecurityToken
}
}

Просмотреть файл

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public class MockURLSession : URLSession {
public var uploadTaskParams = [(request: URLRequest, bodyData: Data?)]()
public var uploadTaskCompletions = [(response: URLResponse?, error: Error?)]()
open override func uploadTask(with request: URLRequest, from bodyData: Data?, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask {
uploadTaskParams.append((request, bodyData))
let comp = uploadTaskCompletions.removeFirst()
completionHandler(nil, comp.response, comp.error)
return MockURLSessionUploadTask()
}
}

Просмотреть файл

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
public class MockURLSessionUploadTask : URLSessionUploadTask {
open override func resume() {
}
}

Просмотреть файл

@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
import Quick
import Nimble
class SharedAccessSignatureTokenProviderSpec: QuickSpec {
override func spec() {
describe("SharedAccessSignatureTokenProvider") {
context("init with shared access signature is called") {
context("with an invalid shared access signature") {
it("throws the expected error") {
expect { try SharedAccessSignatureTokenProvider(sharedAccessSignature: "INVALID_TOKEN") }.to(throwError(EventHubsTokenError.invalidTokenString(tokenString: "INVALID_TOKEN")))
}
}
context("with a shared access signature missing the signed key name") {
it("throws the expected error") {
expect { try SharedAccessSignatureToken.validate(sharedAccessSignature: "SharedAccessSignature sr=signedResource&sig=signature&se=0") }.to(throwError(EventHubsTokenError.missingTokenField(tokenField: "skn")))
}
}
context("with a shared access signature missing the signed expiry") {
it("throws the expected error") {
expect { try SharedAccessSignatureToken.validate(sharedAccessSignature: "SharedAccessSignature sr=signedResource&sig=signature&skn=signedKeyName") }.to(throwError(EventHubsTokenError.missingTokenField(tokenField: "se")))
}
}
}
context ("is initialized with key name and shared access key") {
var provider = SharedAccessSignatureTokenProvider(keyName: "keyName", sharedAccessKey: "sharedAccessKey")
context("getToken is called") {
context("with a valid url") {
let dateFactory = MockDateFactory()
provider.dateFactory = dateFactory
let date = Date(timeIntervalSince1970: TimeInterval(1567000000))
dateFactory.nowReturns.append(date)
let token = try? provider.getToken(appliesTo: URL(string: "https://test.com/test")!, timeToLive: nil)
it ("provides a token with the expected tokenValue") {
expect(token?.tokenValue) == "SharedAccessSignature sr=test.com%2ftest%2f&sig=5YJxMk3FHCFoymb4CCeRv9AMfrFen3XV0353eiIyw5I%3D&se=1567003600&skn=keyName"
}
it ("provides a token with the expected tokenType") {
expect(token?.tokenType) == "servicebus.windows.net:sastoken"
}
it ("provides a token with the expected expiresAt") {
expect(token?.expiresAt).to(equal(date.addingTimeInterval(TimeInterval(3600))))
}
it ("provides a token with the expected audience") {
expect(token?.audience) == "test.com/test/"
}
}
context ("providing a timeToLive parameter") {
provider = SharedAccessSignatureTokenProvider(keyName: "keyName", sharedAccessKey: "sharedAccessKey")
let dateFactory = MockDateFactory()
provider.dateFactory = dateFactory
let date = Date(timeIntervalSince1970: TimeInterval(1567000000))
dateFactory.nowReturns.append(date)
let token = try? provider.getToken(appliesTo: URL(string: "https://test.com/test")!, timeToLive: TimeInterval(2400))
it ("provides a token with the expected expiresAt") {
expect(token?.expiresAt).to(equal(date.addingTimeInterval(TimeInterval(2400))))
}
}
}
context ("with the tokenTimeToLive parameter not the default value") {
context("getToken is called") {
provider = SharedAccessSignatureTokenProvider(keyName: "keyName", sharedAccessKey: "sharedAccessKey", tokenTimeToLive: TimeInterval(4800))
let dateFactory = MockDateFactory()
provider.dateFactory = dateFactory
let date = Date(timeIntervalSince1970: TimeInterval(1567000000))
dateFactory.nowReturns.append(date)
let token = try? provider.getToken(appliesTo: URL(string: "https://test.com/test")!, timeToLive: nil)
it ("provides a token with the expected expiresAt") {
expect(token?.expiresAt).to(equal(date.addingTimeInterval(TimeInterval(4800))))
}
}
}
context ("with the tokenScope parameter not the default value") {
context("getToken is called") {
provider = SharedAccessSignatureTokenProvider(keyName: "keyName", sharedAccessKey: "sharedAccessKey", tokenTimeToLive: nil, tokenScope: .namespace)
let dateFactory = MockDateFactory()
provider.dateFactory = dateFactory
let date = Date(timeIntervalSince1970: TimeInterval(1567000000))
dateFactory.nowReturns.append(date)
let token = try? provider.getToken(appliesTo: URL(string: "https://test.com/test")!, timeToLive: nil)
it ("provides a token with the expected audience") {
expect(token?.audience) == "test.com/"
}
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,106 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Foundation
import Quick
import Nimble
class SharedAccessSignatureTokenSpec: QuickSpec {
override func spec() {
describe("SharedAccessSignatureToken") {
context("init is called") {
context("with an invalid token string") {
it("throws the expected error") {
expect { try SharedAccessSignatureToken(tokenString: "INVALID_TOKEN") }.to(throwError(EventHubsTokenError.invalidTokenString(tokenString: "INVALID_TOKEN")))
}
}
context("with a valid token string") {
var token: SharedAccessSignatureToken?
it("does not throw an error") {
expect { token = try SharedAccessSignatureToken(tokenString: "SharedAccessSignature sr=signedResource&sig=signature&se=0&skn=signedKeyName") }.toNot(throwError())
}
it("sets the tokenValue property") {
expect(token?.tokenValue) == "SharedAccessSignature sr=signedResource&sig=signature&se=0&skn=signedKeyName"
}
it("sets the expiresAt property") {
expect(token?.expiresAt).to(equal(Date.init(timeIntervalSince1970: 0)))
}
it("sets the audience property") {
expect(token?.audience) == "signedResource"
}
it("sets the tokenType property") {
expect(token?.tokenType) == "servicebus.windows.net:sastoken"
}
}
}
context("validate is called") {
context("with a valid shared access signature") {
it("does not throw an error") {
expect { try SharedAccessSignatureToken.validate(sharedAccessSignature: "SharedAccessSignature sr=signedResource&sig=signature&se=0&skn=signedKeyName") }.toNot(throwError())
}
}
context("with a shared access signature missing the signed resource") {
it("throws the expected error") {
expect { try SharedAccessSignatureToken.validate(sharedAccessSignature: "SharedAccessSignature sig=signature&se=0&skn=signedKeyName") }.to(throwError(EventHubsTokenError.missingTokenField(tokenField: "sr")))
}
}
context("with a shared access signature missing the signature") {
it("throws the expected error") {
expect { try SharedAccessSignatureToken.validate(sharedAccessSignature: "SharedAccessSignature sr=signedResource&se=0&skn=signedKeyName") }.to(throwError(EventHubsTokenError.missingTokenField(tokenField: "sig")))
}
}
context("with a shared access signature missing the signed expiry") {
it("throws the expected error") {
expect { try SharedAccessSignatureToken.validate(sharedAccessSignature: "SharedAccessSignature sr=signedResource&sig=signature&skn=signedKeyName") }.to(throwError(EventHubsTokenError.missingTokenField(tokenField: "se")))
}
}
context("with a shared access signature missing the signed key name") {
it("throws the expected error") {
expect { try SharedAccessSignatureToken.validate(sharedAccessSignature: "SharedAccessSignature sr=signedResource&sig=signature&se=0") }.to(throwError(EventHubsTokenError.missingTokenField(tokenField: "skn")))
}
}
}
context("getAudienceFromToken is called") {
context("with a valid token string") {
it("returns the expected audience") {
expect { return try SharedAccessSignatureToken.getAudienceFromToken(tokenString: "SharedAccessSignature sr=signedResource&sig=signature&se=0&skn=signedKeyName") }.to(equal("signedResource"))
}
}
context("with a token string missing the signed resource") {
it("throws the expected error") {
expect { try SharedAccessSignatureToken.getAudienceFromToken(tokenString: "SharedAccessSignature sig=signature&se=0skn=signedKeyName") }.to(throwError(EventHubsTokenError.missingTokenField(tokenField: "sr")))
}
}
}
context("getExpirationDateFromToken is called") {
context("with a valid token string") {
it("returns the expected date") {
expect { return try SharedAccessSignatureToken.getExpirationDateFromToken(tokenString: "SharedAccessSignature sr=signedResource&sig=signature&se=0&skn=signedKeyName") }.to(equal(Date.init(timeIntervalSince1970: 0)))
}
}
context("with a token string missing the signed expiry") {
it("throws the expected error") {
expect { try SharedAccessSignatureToken.getExpirationDateFromToken(tokenString: "SharedAccessSignature sr=signedResource&sig=signature&skn=signedKeyName") }.to(throwError(EventHubsTokenError.missingTokenField(tokenField: "se")))
}
}
context("with a token string where the signed expiry value is not numerical") {
it("throws the expected error") {
expect { try SharedAccessSignatureToken.getExpirationDateFromToken(tokenString: "SharedAccessSignature sr=signedResource&sig=signature&se=Zero&skn=signedKeyName") }.to(throwError(EventHubsTokenError.missingTokenField(tokenField: "se")))
}
}
}
context("getKeyNameFromToken is called") {
context("with a valid token string") {
it("returns the expected signed key name") {
expect { return try SharedAccessSignatureToken.getKeyNameFromToken(tokenString: "SharedAccessSignature sr=signedResource&sig=signature&se=0&skn=signedKeyName") }.to(equal("signedKeyName"))
}
}
context("with a token string missing the signed key name") {
it("throws the expected error") {
expect { try SharedAccessSignatureToken.getKeyNameFromToken(tokenString: "SharedAccessSignature sr=signedResource&sig=signature&se=0") }.to(throwError(EventHubsTokenError.missingTokenField(tokenField: "skn")))
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Quick
import Nimble
import Foundation
class UrlExtensionsSpec: QuickSpec {
override func spec() {
describe("UrlExtensions") {
let url = URL(string: "https://test.com/entity")
context("appliesToUriString is called") {
context("with an entity token scope") {
context("ensuring a trailng slash") {
it("returns the expected uri string") {
expect(url?.appliesToUriString(tokenScope: .entity, ensureTrailingSlash: true)) == "test.com/entity/"
}
}
context("not ensuring a trailng slash") {
it("returns the expected uri string") {
expect(url?.appliesToUriString(tokenScope: .entity, ensureTrailingSlash: false)) == "test.com/entity"
}
}
}
context("with a namespace token scope") {
context("ensuring a trailng slash") {
it("returns the expected uri string") {
expect(url?.appliesToUriString(tokenScope: .namespace, ensureTrailingSlash: true)) == "test.com/"
}
}
context("not ensuring a trailng slash") {
it("returns the expected uri string") {
expect(url?.appliesToUriString(tokenScope: .namespace, ensureTrailingSlash: false)) == "test.com"
}
}
}
}
}
}
}