Skip to content

Commit 7a08f17

Browse files
committed
RemoteConfig Swift pod and Codable
1 parent ac4df1f commit 7a08f17

File tree

7 files changed

+342
-64
lines changed

7 files changed

+342
-64
lines changed

.github/workflows/remoteconfig.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,12 @@ jobs:
3434
if: matrix.target == 'iOS'
3535
run: ([ -z $plist_secret ] || scripts/generate_access_token.sh "$plist_secret" scripts/gha-encrypted/RemoteConfigSwiftAPI/ServiceAccount.json.gpg
3636
FirebaseRemoteConfig/Tests/SwiftAPI/AccessToken.json)
37-
- name: BuildAndUnitTest # can be replaced with pod lib lint with CocoaPods 1.10
38-
run: scripts/third_party/travis/retry.sh scripts/build.sh RemoteConfig ${{ matrix.target }} unit
3937
- name: Fake Console API Tests
4038
run: scripts/third_party/travis/retry.sh scripts/build.sh RemoteConfig iOS fakeconsole
41-
- name: IntegrationTest
42-
if: matrix.target == 'iOS'
43-
run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/build.sh RemoteConfig iOS integration)
39+
# Disable - pending resolution of #9086
40+
# - name: IntegrationTest
41+
# if: matrix.target == 'iOS'
42+
# run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/build.sh RemoteConfig iOS integration)
4443

4544
pod-lib-lint:
4645
# Don't run on private repo unless it is a PR.
@@ -50,13 +49,14 @@ jobs:
5049
strategy:
5150
matrix:
5251
target: [ios, tvos, macos, watchos]
52+
podspec: [FirebaseRemoteConfig.podspec, FirebaseRemoteConfigSwift.podspec --skip-tests]
5353
steps:
5454
- uses: actions/checkout@v2
5555
- name: Setup Bundler
5656
run: scripts/setup_bundler.sh
5757
- name: Build and test
5858
run: |
59-
scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseRemoteConfig.podspec --skip-tests --platforms=${{ matrix.target }}
59+
scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb ${{ matrix.podspec }} --platforms=${{ matrix.target }}
6060
6161
spm:
6262
# Don't run on private repo unless it is a PR.

FirebaseRemoteConfig.podspec

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ app update.
5252

5353
s.test_spec 'unit' do |unit_tests|
5454
unit_tests.scheme = { :code_coverage => true }
55+
unit_tests.platforms = {
56+
:ios => ios_deployment_target,
57+
:osx => osx_deployment_target,
58+
:tvos => tvos_deployment_target
59+
}
5560
# TODO(dmandar) - Update or delete the commented files.
5661
unit_tests.source_files =
5762
'FirebaseRemoteConfig/Tests/Unit/FIRRemoteConfigComponentTest.m',
@@ -79,40 +84,4 @@ app update.
7984
unit_tests.requires_arc = true
8085
end
8186

82-
# Run Swift API tests on a real backend.
83-
s.test_spec 'swift-api-tests' do |swift_api|
84-
swift_api.scheme = { :code_coverage => true }
85-
swift_api.platforms = {
86-
:ios => ios_deployment_target,
87-
:osx => osx_deployment_target,
88-
:tvos => tvos_deployment_target
89-
}
90-
swift_api.source_files = 'FirebaseRemoteConfig/Tests/SwiftAPI/*.swift',
91-
'FirebaseRemoteConfig/Tests/FakeUtils/*.[hm]',
92-
'FirebaseRemoteConfig/Tests/FakeUtils/*.swift'
93-
swift_api.requires_app_host = true
94-
swift_api.pod_target_xcconfig = {
95-
'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h'
96-
}
97-
swift_api.dependency 'OCMock'
98-
end
99-
100-
# Run Swift API tests and tests requiring console changes on a Fake Console.
101-
s.test_spec 'fake-console-tests' do |fake_console|
102-
fake_console.scheme = { :code_coverage => true }
103-
fake_console.platforms = {
104-
:ios => ios_deployment_target,
105-
:osx => osx_deployment_target,
106-
:tvos => tvos_deployment_target
107-
}
108-
fake_console.source_files = 'FirebaseRemoteConfig/Tests/SwiftAPI/*.swift',
109-
'FirebaseRemoteConfig/Tests/FakeUtils/*.[hm]',
110-
'FirebaseRemoteConfig/Tests/FakeUtils/*.swift',
111-
'FirebaseRemoteConfig/Tests/FakeConsole/*.swift'
112-
fake_console.requires_app_host = true
113-
fake_console.pod_target_xcconfig = {
114-
'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h'
115-
}
116-
fake_console.dependency 'OCMock'
117-
end
11887
end
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import FirebaseRemoteConfig
16+
import FirebaseRemoteConfigSwift
17+
18+
import XCTest
19+
20+
/// String constants used for testing.
21+
private enum Constants {
22+
static let jsonKey = "Recipe"
23+
static let recipe = ["recipeName": "PB&J",
24+
"ingredients": ["bread", "peanut butter", "jelly"],
25+
"cookTime": 7] as [String: AnyHashable]
26+
static let nonJsonKey = "notJSON"
27+
static let nonJsonValue = "notJSON"
28+
}
29+
30+
#if compiler(>=5.5) && canImport(_Concurrency)
31+
@available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
32+
class CodableTests: APITestBase {
33+
var console: RemoteConfigConsole!
34+
35+
override func setUp() {
36+
super.setUp()
37+
do {
38+
let jsonData = try JSONSerialization.data(
39+
withJSONObject: Constants.recipe,
40+
options: .prettyPrinted
41+
)
42+
guard let jsonValue = String(data: jsonData, encoding: .ascii) else {
43+
fatalError("Failed to make json Value from jsonData")
44+
}
45+
if APITests.useFakeConfig {
46+
fakeConsole.config = [Constants.jsonKey: jsonValue,
47+
Constants.nonJsonKey: Constants.nonJsonValue]
48+
} else {
49+
console = RemoteConfigConsole()
50+
console.updateRemoteConfigValue(jsonValue, forKey: Constants.jsonKey)
51+
console.updateRemoteConfigValue(Constants.nonJsonKey, forKey: Constants.nonJsonValue)
52+
}
53+
} catch {
54+
print("Failed to initialize json data in fake Console \(error.localizedDescription)")
55+
}
56+
}
57+
58+
override func tearDown() {
59+
super.tearDown()
60+
61+
// If using RemoteConfigConsole, reset remote config values.
62+
if !APITests.useFakeConfig {
63+
console.removeRemoteConfigValue(forKey: Constants.jsonKey)
64+
}
65+
}
66+
67+
func testFetchAndActivateWithoutCodable() async throws {
68+
let status = try await config.fetchAndActivate()
69+
XCTAssertEqual(status, .successFetchedFromRemote)
70+
guard let dict = config[Constants.jsonKey].jsonValue as? [String: AnyHashable] else {
71+
XCTFail("Failed to extract json")
72+
return
73+
}
74+
XCTAssertEqual(dict["recipeName"], "PB&J")
75+
XCTAssertEqual(dict["ingredients"], ["bread", "peanut butter", "jelly"])
76+
XCTAssertEqual(dict["cookTime"], 7)
77+
XCTAssertEqual(
78+
config[Constants.jsonKey].jsonValue as! [String: AnyHashable],
79+
Constants.recipe
80+
)
81+
}
82+
83+
struct Recipe: Decodable {
84+
var recipeName: String
85+
var ingredients: [String]
86+
var cookTime: Int
87+
}
88+
89+
func testFetchAndActivateWithCodable() async throws {
90+
let status = try await config.fetchAndActivate()
91+
XCTAssertEqual(status, .successFetchedFromRemote)
92+
guard let value = try config[Constants.jsonKey].decode(asType: Recipe?.self) else {
93+
XCTFail("Failed to extract json")
94+
return
95+
}
96+
XCTAssertEqual(value.recipeName, "PB&J")
97+
XCTAssertEqual(value.ingredients, ["bread", "peanut butter", "jelly"])
98+
XCTAssertEqual(value.cookTime, 7)
99+
}
100+
101+
func testFetchAndActivateWithCodableBadJson() async throws {
102+
let status = try await config.fetchAndActivate()
103+
XCTAssertEqual(status, .successFetchedFromRemote)
104+
do {
105+
_ = try config[Constants.nonJsonKey].decode(asType: String?.self)
106+
} catch RemoteConfigCodableError.jsonValueError {
107+
return
108+
}
109+
XCTFail("Failed to catch trying to decode non-JSON key as JSON")
110+
}
111+
112+
struct DataTestDefaults: Encodable {
113+
var bool: Bool
114+
var int: Int32
115+
var long: Int64
116+
var string: String
117+
}
118+
119+
func testSetEncodeableDefaults() throws {
120+
let data = DataTestDefaults(
121+
bool: true,
122+
int: 2,
123+
long: 9_876_543_210,
124+
string: "four"
125+
)
126+
try config.setDefaults(from: data)
127+
let boolValue = try XCTUnwrap(config.defaultValue(forKey: "bool")).numberValue.boolValue
128+
XCTAssertTrue(boolValue)
129+
let intValue = try XCTUnwrap(config.defaultValue(forKey: "int")).numberValue.intValue
130+
XCTAssertEqual(intValue, 2)
131+
let longValue = try XCTUnwrap(config.defaultValue(forKey: "long")).numberValue.int64Value
132+
XCTAssertEqual(longValue, 9_876_543_210)
133+
let stringValue = try XCTUnwrap(config.defaultValue(forKey: "string")).stringValue
134+
XCTAssertEqual(stringValue, "four")
135+
}
136+
}
137+
#endif

FirebaseRemoteConfigSwift.podspec

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
Pod::Spec.new do |s|
2+
s.name = 'FirebaseRemoteConfigSwift'
3+
s.version = '8.10.0-beta'
4+
s.summary = 'Swift Extensions for Firebase Remote Config'
5+
6+
s.description = <<-DESC
7+
Firebase Remote Config is a cloud service that lets you change the
8+
appearance and behavior of your app without requiring users to download an
9+
app update.
10+
DESC
11+
12+
13+
s.homepage = 'https://developers.google.com/'
14+
s.license = { :type => 'Apache', :file => 'LICENSE' }
15+
s.authors = 'Google, Inc.'
16+
17+
s.source = {
18+
:git => 'https://github.com/Firebase/firebase-ios-sdk.git',
19+
:tag => 'CocoaPods-' + s.version.to_s
20+
}
21+
22+
s.swift_version = '5.0'
23+
24+
ios_deployment_target = '10.0'
25+
osx_deployment_target = '10.12'
26+
tvos_deployment_target = '10.0'
27+
watchos_deployment_target = '6.0'
28+
29+
s.ios.deployment_target = ios_deployment_target
30+
s.osx.deployment_target = osx_deployment_target
31+
s.tvos.deployment_target = tvos_deployment_target
32+
s.watchos.deployment_target = watchos_deployment_target
33+
34+
s.cocoapods_version = '>= 1.4.0'
35+
s.prefix_header_file = false
36+
37+
s.source_files = [
38+
'FirebaseRemoteConfigSwift/Sources/*.swift',
39+
]
40+
41+
s.dependency 'FirebaseRemoteConfig', '~> 8.10'
42+
s.dependency 'FirebaseSharedSwift', '~> 8.10-beta'
43+
44+
45+
# Run Swift API tests on a real backend.
46+
s.test_spec 'swift-api-tests' do |swift_api|
47+
swift_api.scheme = { :code_coverage => true }
48+
swift_api.platforms = {
49+
:ios => ios_deployment_target,
50+
:osx => osx_deployment_target,
51+
:tvos => tvos_deployment_target
52+
}
53+
swift_api.source_files = 'FirebaseRemoteConfig/Tests/SwiftAPI/*.swift',
54+
'FirebaseRemoteConfig/Tests/FakeUtils/*.[hm]',
55+
'FirebaseRemoteConfig/Tests/FakeUtils/*.swift'
56+
swift_api.requires_app_host = true
57+
swift_api.pod_target_xcconfig = {
58+
'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h',
59+
'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"'
60+
}
61+
swift_api.dependency 'OCMock'
62+
end
63+
64+
# Run Swift API tests and tests requiring console changes on a Fake Console.
65+
s.test_spec 'fake-console-tests' do |fake_console|
66+
fake_console.scheme = { :code_coverage => true }
67+
fake_console.platforms = {
68+
:ios => ios_deployment_target,
69+
:osx => osx_deployment_target,
70+
:tvos => tvos_deployment_target
71+
}
72+
fake_console.source_files = 'FirebaseRemoteConfig/Tests/SwiftAPI/*.swift',
73+
'FirebaseRemoteConfig/Tests/FakeUtils/*.[hm]',
74+
'FirebaseRemoteConfig/Tests/FakeUtils/*.swift',
75+
'FirebaseRemoteConfig/Tests/FakeConsole/*.swift'
76+
fake_console.requires_app_host = true
77+
fake_console.pod_target_xcconfig = {
78+
'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h',
79+
'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"'
80+
}
81+
fake_console.dependency 'OCMock'
82+
end
83+
end
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import Foundation
18+
import FirebaseRemoteConfig
19+
import FirebaseSharedSwift
20+
21+
public enum RemoteConfigCodableError: Error {
22+
case jsonValueError
23+
}
24+
25+
public extension RemoteConfigValue {
26+
/**
27+
* Extracts a RemoteConfigValue JSON-encoded object and decodes it to the requested type
28+
*
29+
* - Parameter valueType: The type to decode the JSON-object to
30+
*/
31+
func decode<Value: Decodable>(asType: Value.Type) throws -> Value {
32+
guard let jsonValue = self.jsonValue else {
33+
throw RemoteConfigCodableError.jsonValueError
34+
}
35+
return try FirebaseDataDecoder().decode(Value.self, from: jsonValue)
36+
}
37+
}
38+
39+
public extension RemoteConfig {
40+
/**
41+
* Sets config defaults from an encodable struct.
42+
*
43+
* - Parameter value: The object to use to set the defaults.
44+
*/
45+
func setDefaults<Value: Encodable>(from value: Value) throws {
46+
let encoded = try FirebaseDataEncoder().encode(value) as! [String: NSObject]
47+
setDefaults(encoded)
48+
}
49+
}

0 commit comments

Comments
 (0)