Skip to content

Commit adb166c

Browse files
author
Di Wu
authored
fix(datastore): support CPK sort key with AWS date types (#3020)
1 parent 7e859db commit adb166c

File tree

12 files changed

+647
-0
lines changed

12 files changed

+647
-0
lines changed

AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ModelIdDecorator.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ fileprivate extension ModelIdDecorator {
103103
return data
104104
case let data as Bool:
105105
return data
106+
case let data as Temporal.DateTime:
107+
return data.iso8601String
108+
case let data as Temporal.Date:
109+
return data.iso8601String
110+
case let data as Temporal.Time:
111+
return data.iso8601String
106112
default:
107113
return "\(persistable)"
108114
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
9+
import Foundation
10+
import Combine
11+
import XCTest
12+
import AWSAPIPlugin
13+
import AWSDataStorePlugin
14+
15+
@testable import Amplify
16+
@testable import DataStoreHostApp
17+
18+
fileprivate struct TestModels: AmplifyModelRegistration {
19+
func registerModels(registry: ModelRegistry.Type) {
20+
ModelRegistry.register(modelType: Post13.self)
21+
}
22+
23+
var version: String = "test"
24+
}
25+
26+
class AWSDataStoreDateTimeSortKeyTest: XCTestCase {
27+
let configFile = "testconfiguration/AWSDataStoreCategoryPluginPrimaryKeyIntegrationTests-amplifyconfiguration"
28+
29+
override func setUp() async throws {
30+
continueAfterFailure = true
31+
let config = try TestConfigHelper.retrieveAmplifyConfiguration(forResource: configFile)
32+
try Amplify.add(plugin: AWSAPIPlugin(
33+
sessionFactory: AmplifyURLSessionFactory())
34+
)
35+
try Amplify.add(plugin: AWSDataStorePlugin(
36+
modelRegistration: TestModels(),
37+
configuration: .custom(syncMaxRecords: 100)
38+
))
39+
Amplify.Logging.logLevel = .verbose
40+
try Amplify.configure(config)
41+
}
42+
43+
override func tearDown() async throws {
44+
try await Amplify.DataStore.clear()
45+
await Amplify.reset()
46+
try await Task.sleep(seconds: 1)
47+
}
48+
49+
func waitDataStoreReady() async throws {
50+
let ready = expectation(description: "DataStore is ready")
51+
var requests: Set<AnyCancellable> = []
52+
Amplify.Hub.publisher(for: .dataStore)
53+
.filter { $0.eventName == HubPayload.EventName.DataStore.ready }
54+
.sink { _ in
55+
ready.fulfill()
56+
}
57+
.store(in: &requests)
58+
59+
try await Amplify.DataStore.start()
60+
await fulfillment(of: [ready], timeout: 60)
61+
}
62+
63+
func testCreateModel_withSortKeyInAWSDateTimeType_success() async throws {
64+
try await waitDataStoreReady()
65+
var requests: Set<AnyCancellable> = []
66+
let post = Post13(postId: UUID().uuidString, sk: Temporal.DateTime.now())
67+
let postCreated = expectation(description: "Post is created")
68+
postCreated.assertForOverFulfill = false
69+
Amplify.Hub.publisher(for: .dataStore)
70+
.filter { $0.eventName == HubPayload.EventName.DataStore.syncReceived }
71+
.compactMap { $0.data as? MutationEvent }
72+
.filter { $0.modelId == post.identifier }
73+
.sink { _ in
74+
postCreated.fulfill()
75+
}.store(in: &requests)
76+
77+
try await Amplify.DataStore.save(post)
78+
await fulfillment(of: [postCreated], timeout: 5)
79+
}
80+
81+
func testQueryCreatedModel_withSortKeyInAWSDateTimeType_success() async throws {
82+
try await waitDataStoreReady()
83+
var requests: Set<AnyCancellable> = []
84+
let post = Post13(postId: UUID().uuidString, sk: Temporal.DateTime.now())
85+
let postCreated = expectation(description: "Post is created")
86+
postCreated.assertForOverFulfill = false
87+
Amplify.Hub.publisher(for: .dataStore)
88+
.filter { $0.eventName == HubPayload.EventName.DataStore.syncReceived }
89+
.compactMap { $0.data as? MutationEvent }
90+
.filter { $0.modelId == post.identifier }
91+
.sink { _ in
92+
postCreated.fulfill()
93+
}.store(in: &requests)
94+
95+
try await Amplify.DataStore.save(post)
96+
await fulfillment(of: [postCreated], timeout: 5)
97+
98+
let queryResult = try await Amplify.API.query(
99+
request: .get(
100+
Post13.self,
101+
byIdentifier: .identifier(postId: post.postId, sk: post.sk)
102+
)
103+
)
104+
105+
switch queryResult {
106+
case .success(let queriedPost):
107+
XCTAssertEqual(post.identifier, queriedPost!.identifier)
108+
case .failure(let error):
109+
XCTFail("Failed to query comment \(error)")
110+
}
111+
}
112+
113+
}
114+
115+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// swiftlint:disable all
2+
import Amplify
3+
import Foundation
4+
5+
extension Post13 {
6+
// MARK: - CodingKeys
7+
public enum CodingKeys: String, ModelKey {
8+
case postId
9+
case sk
10+
case createdAt
11+
case updatedAt
12+
}
13+
14+
public static let keys = CodingKeys.self
15+
// MARK: - ModelSchema
16+
17+
public static let schema = defineSchema { model in
18+
let post13 = Post13.keys
19+
20+
model.pluralName = "Post13s"
21+
22+
model.attributes(
23+
.index(fields: ["postId", "sk"], name: nil),
24+
.primaryKey(fields: [post13.postId, post13.sk])
25+
)
26+
27+
model.fields(
28+
.field(post13.postId, is: .required, ofType: .string),
29+
.field(post13.sk, is: .required, ofType: .dateTime),
30+
.field(post13.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime),
31+
.field(post13.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime)
32+
)
33+
}
34+
}
35+
36+
extension Post13: ModelIdentifiable {
37+
public typealias IdentifierFormat = ModelIdentifierFormat.Custom
38+
public typealias IdentifierProtocol = ModelIdentifier<Self, ModelIdentifierFormat.Custom>
39+
}
40+
41+
extension Post13.IdentifierProtocol {
42+
public static func identifier(postId: String,
43+
sk: Temporal.DateTime) -> Self {
44+
.make(fields:[(name: "postId", value: postId), (name: "sk", value: sk)])
45+
}
46+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// swiftlint:disable all
2+
import Amplify
3+
import Foundation
4+
5+
public struct Post13: Model {
6+
public let postId: String
7+
public let sk: Temporal.DateTime
8+
public var createdAt: Temporal.DateTime?
9+
public var updatedAt: Temporal.DateTime?
10+
11+
public init(postId: String,
12+
sk: Temporal.DateTime) {
13+
self.init(postId: postId,
14+
sk: sk,
15+
createdAt: nil,
16+
updatedAt: nil)
17+
}
18+
internal init(postId: String,
19+
sk: Temporal.DateTime,
20+
createdAt: Temporal.DateTime? = nil,
21+
updatedAt: Temporal.DateTime? = nil) {
22+
self.postId = postId
23+
self.sk = sk
24+
self.createdAt = createdAt
25+
self.updatedAt = updatedAt
26+
}
27+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
9+
import Foundation
10+
import Combine
11+
import XCTest
12+
import AWSAPIPlugin
13+
import AWSDataStorePlugin
14+
15+
@testable import Amplify
16+
@testable import DataStoreHostApp
17+
18+
fileprivate struct TestModels: AmplifyModelRegistration {
19+
func registerModels(registry: ModelRegistry.Type) {
20+
ModelRegistry.register(modelType: Post14.self)
21+
}
22+
23+
var version: String = "test"
24+
}
25+
26+
class AWSDataStoreDateSortKeyTest: XCTestCase {
27+
let configFile = "testconfiguration/AWSDataStoreCategoryPluginPrimaryKeyIntegrationTests-amplifyconfiguration"
28+
29+
override func setUp() async throws {
30+
continueAfterFailure = true
31+
let config = try TestConfigHelper.retrieveAmplifyConfiguration(forResource: configFile)
32+
try Amplify.add(plugin: AWSAPIPlugin(
33+
sessionFactory: AmplifyURLSessionFactory())
34+
)
35+
try Amplify.add(plugin: AWSDataStorePlugin(
36+
modelRegistration: TestModels(),
37+
configuration: .custom(syncMaxRecords: 100)
38+
))
39+
Amplify.Logging.logLevel = .verbose
40+
try Amplify.configure(config)
41+
}
42+
43+
override func tearDown() async throws {
44+
try await Amplify.DataStore.clear()
45+
await Amplify.reset()
46+
try await Task.sleep(seconds: 1)
47+
}
48+
49+
func waitDataStoreReady() async throws {
50+
let ready = expectation(description: "DataStore is ready")
51+
var requests: Set<AnyCancellable> = []
52+
Amplify.Hub.publisher(for: .dataStore)
53+
.filter { $0.eventName == HubPayload.EventName.DataStore.ready }
54+
.sink { _ in
55+
ready.fulfill()
56+
}
57+
.store(in: &requests)
58+
59+
try await Amplify.DataStore.start()
60+
await fulfillment(of: [ready], timeout: 60)
61+
}
62+
63+
func testCreateModel_withSortKeyInAWSDateType_success() async throws {
64+
try await waitDataStoreReady()
65+
var requests: Set<AnyCancellable> = []
66+
let post = Post14(postId: UUID().uuidString, sk: Temporal.Date.now())
67+
let postCreated = expectation(description: "Post is created")
68+
postCreated.assertForOverFulfill = false
69+
Amplify.Hub.publisher(for: .dataStore)
70+
.filter { $0.eventName == HubPayload.EventName.DataStore.syncReceived }
71+
.compactMap { $0.data as? MutationEvent }
72+
.filter { $0.modelId == post.identifier }
73+
.sink { _ in
74+
postCreated.fulfill()
75+
}.store(in: &requests)
76+
77+
try await Amplify.DataStore.save(post)
78+
await fulfillment(of: [postCreated], timeout: 5)
79+
}
80+
81+
func testQueryCreatedModel_withSortKeyInAWSDateType_success() async throws {
82+
try await waitDataStoreReady()
83+
var requests: Set<AnyCancellable> = []
84+
let post = Post14(postId: UUID().uuidString, sk: Temporal.Date.now())
85+
let postCreated = expectation(description: "Post is created")
86+
postCreated.assertForOverFulfill = false
87+
Amplify.Hub.publisher(for: .dataStore)
88+
.filter { $0.eventName == HubPayload.EventName.DataStore.syncReceived }
89+
.compactMap { $0.data as? MutationEvent }
90+
.filter { $0.modelId == post.identifier }
91+
.sink { _ in
92+
postCreated.fulfill()
93+
}.store(in: &requests)
94+
95+
try await Amplify.DataStore.save(post)
96+
await fulfillment(of: [postCreated], timeout: 5)
97+
98+
let queryResult = try await Amplify.API.query(
99+
request: .get(
100+
Post14.self,
101+
byIdentifier: .identifier(postId: post.postId, sk: post.sk)
102+
)
103+
)
104+
105+
switch queryResult {
106+
case .success(let queriedPost):
107+
XCTAssertEqual(post.identifier, queriedPost!.identifier)
108+
case .failure(let error):
109+
XCTFail("Failed to query comment \(error)")
110+
}
111+
}
112+
113+
}
114+
115+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// swiftlint:disable all
2+
import Amplify
3+
import Foundation
4+
5+
extension Post14 {
6+
// MARK: - CodingKeys
7+
public enum CodingKeys: String, ModelKey {
8+
case postId
9+
case sk
10+
case createdAt
11+
case updatedAt
12+
}
13+
14+
public static let keys = CodingKeys.self
15+
// MARK: - ModelSchema
16+
17+
public static let schema = defineSchema { model in
18+
let post14 = Post14.keys
19+
20+
model.pluralName = "Post14s"
21+
22+
model.attributes(
23+
.index(fields: ["postId", "sk"], name: nil),
24+
.primaryKey(fields: [post14.postId, post14.sk])
25+
)
26+
27+
model.fields(
28+
.field(post14.postId, is: .required, ofType: .string),
29+
.field(post14.sk, is: .required, ofType: .date),
30+
.field(post14.createdAt, is: .optional, isReadOnly: true, ofType: .dateTime),
31+
.field(post14.updatedAt, is: .optional, isReadOnly: true, ofType: .dateTime)
32+
)
33+
}
34+
}
35+
36+
extension Post14: ModelIdentifiable {
37+
public typealias IdentifierFormat = ModelIdentifierFormat.Custom
38+
public typealias IdentifierProtocol = ModelIdentifier<Self, ModelIdentifierFormat.Custom>
39+
}
40+
41+
extension Post14.IdentifierProtocol {
42+
public static func identifier(postId: String,
43+
sk: Temporal.Date) -> Self {
44+
.make(fields:[(name: "postId", value: postId), (name: "sk", value: sk)])
45+
}
46+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// swiftlint:disable all
2+
import Amplify
3+
import Foundation
4+
5+
public struct Post14: Model {
6+
public let postId: String
7+
public let sk: Temporal.Date
8+
public var createdAt: Temporal.DateTime?
9+
public var updatedAt: Temporal.DateTime?
10+
11+
public init(postId: String,
12+
sk: Temporal.Date) {
13+
self.init(postId: postId,
14+
sk: sk,
15+
createdAt: nil,
16+
updatedAt: nil)
17+
}
18+
internal init(postId: String,
19+
sk: Temporal.Date,
20+
createdAt: Temporal.DateTime? = nil,
21+
updatedAt: Temporal.DateTime? = nil) {
22+
self.postId = postId
23+
self.sk = sk
24+
self.createdAt = createdAt
25+
self.updatedAt = updatedAt
26+
}
27+
}

0 commit comments

Comments
 (0)