Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MarkOrderAsReadUseCase tests #13946

Merged
merged 13 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Networking/Networking/Network/MockNetwork.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ class MockNetwork: Network {
///
var requestsForResponseData = [URLRequestConvertible]()

/// Number of notification objects in notifications-load-all.json file.
///
static let notificationLoadAllJSONCount = 46

/// Note: If the useResponseQueue param is `true`, any responses added via `simulateResponse` will stored in a FIFO queue
/// and used once for a matching request (then removed from the queue). Subsequent requests will use the next response in the queue, and so on.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ final class NoteListMapperTests: XCTestCase {
/// Verifies that all of the Sample Notifications are properly parsed.
///
func test_sample_notifications_are_properly_decoded() {
XCTAssertEqual(sampleNotes.count, 44)
XCTAssertEqual(sampleNotes.count, MockNetwork.notificationLoadAllJSONCount)
}

/// Verifies that the Broken Notification documents are properly parsed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ final class NotificationsRemoteTests: XCTestCase {
XCTAssertTrue(result.isSuccess)

let notes = try result.get()
XCTAssertEqual(notes.count, 44)
XCTAssertEqual(notes.count, MockNetwork.notificationLoadAllJSONCount)
}

/// Verifies that `loadHashes` properly returns all of the retrieved hashes.
Expand Down
96 changes: 96 additions & 0 deletions Networking/NetworkingTests/Responses/notifications-load-all.json
Original file line number Diff line number Diff line change
Expand Up @@ -7671,6 +7671,102 @@
}
]
},
{
"id": 100041,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unread note with order id

"note_hash": 987654,
"type": "store_order",
"read": false,
"noticon": "\uf447",
"timestamp": "2018-10-22T15:29:36+00:00",
"icon": "https:\/\/s.wp.com\/wp-content\/mu-plugins\/notes\/images\/update-payment-2x.png",
"url": "https:\/\/aaaaaaaaa.mystagingwebsite.com\/wp-admin\/post.php?post=1476&action=edit",
"subject": [{
"text": "\ud83c\udf89 You have a new order!"
}, {
"text": "Someone placed a $20.00 order from Bananza World",
"ranges": [{
"type": "site",
"indices": [35, 48],
"url": "https:\/\/somewhere.tld",
"id": 123456
}]
}],
"body": [{
"text": "",
"media": [{
"type": "image",
"indices": [0, 0],
"height": "48",
"width": "48",
"url": "https:\/\/s.wp.com\/wp-content\/mu-plugins\/notes\/images\/store-cart-icon.png"
}]
}, {
"text": "Order Number: 1476\nDate: October 22, 2018\nTotal: $20.00\nPayment Method: Credit Card (Stripe)\nShipping Method: Free shipping"
}, {
"text": "Products:\n\nAlmonds \u00d7 1\n"
}, {
"text": "\u2139\ufe0f View Order",
"ranges": [{
"url": "https:\/\/somewhere.tld\/wp-admin\/post.php?post=1476&action=edit",
"indices": [0, 13],
"type": "link"
}]
}],
"meta": {
"ids": {
"order": 123456
}
},
"title": "New Order"
},
{
"id": 100042,
"note_hash": 987654,
"type": "store_order",
"read": true,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read note with order id

"noticon": "\uf447",
"timestamp": "2018-10-22T15:29:36+00:00",
"icon": "https:\/\/s.wp.com\/wp-content\/mu-plugins\/notes\/images\/update-payment-2x.png",
"url": "https:\/\/aaaaaaaaa.mystagingwebsite.com\/wp-admin\/post.php?post=1476&action=edit",
"subject": [{
"text": "\ud83c\udf89 You have a new order!"
}, {
"text": "Someone placed a $20.00 order from Bananza World",
"ranges": [{
"type": "site",
"indices": [35, 48],
"url": "https:\/\/somewhere.tld",
"id": 123456
}]
}],
"body": [{
"text": "",
"media": [{
"type": "image",
"indices": [0, 0],
"height": "48",
"width": "48",
"url": "https:\/\/s.wp.com\/wp-content\/mu-plugins\/notes\/images\/store-cart-icon.png"
}]
}, {
"text": "Order Number: 1476\nDate: October 22, 2018\nTotal: $20.00\nPayment Method: Credit Card (Stripe)\nShipping Method: Free shipping"
}, {
"text": "Products:\n\nAlmonds \u00d7 1\n"
}, {
"text": "\u2139\ufe0f View Order",
"ranges": [{
"url": "https:\/\/somewhere.tld\/wp-admin\/post.php?post=1476&action=edit",
"indices": [0, 13],
"type": "link"
}]
}],
"meta": {
"ids": {
"order": 123456
}
},
"title": "New Order"
},
{
"id": 8401476681,
"type": "blaze_approved_note",
Expand Down
12 changes: 8 additions & 4 deletions WooCommerce/Classes/Model/MarkOrderAsReadUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ struct MarkOrderAsReadUseCase {
switch syncronizedNoteResult {
case .success(let syncronizedNote):
guard let syncronizedNoteOrderID = syncronizedNote.meta.identifier(forKey: .order),
syncronizedNoteOrderID == orderID else {
syncronizedNoteOrderID == orderID,
syncronizedNote.read == false else {
return .failure(MarkOrderAsReadUseCase.Error.noNeedToMarkAsRead)
}

Expand Down Expand Up @@ -88,11 +89,14 @@ struct MarkOrderAsReadUseCase {

switch loadedNotes {
case .success(let notes):
guard let note = notes.first else {
guard let note = notes.first(where: { goalNote in
return goalNote.noteID == noteID
}) else {
return .failure(MarkOrderAsReadUseCase.Error.unavailableNote)
}
guard let syncronizedNoteOrderID = note.meta.identifier(forKey: .order),
syncronizedNoteOrderID == orderID else {
guard let loadedNoteOrderID = note.meta.identifier(forKey: .order),
loadedNoteOrderID == orderID,
note.read == false else {
return .failure(MarkOrderAsReadUseCase.Error.noNeedToMarkAsRead)
}

Expand Down
4 changes: 4 additions & 0 deletions WooCommerce/WooCommerce.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2446,6 +2446,7 @@
DA1D68C22C36F0980097859A /* PointOfSaleAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1D68C12C36F0980097859A /* PointOfSaleAssets.swift */; };
DA25ADDD2C86145E00AE81FE /* MarkOrderAsReadUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA25ADDC2C86145E00AE81FE /* MarkOrderAsReadUseCase.swift */; };
DA25ADDF2C87403900AE81FE /* PushNotificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA25ADDE2C87403900AE81FE /* PushNotificationTests.swift */; };
DA3F99BA2C92F6D30034BDA5 /* MarkOrderAsReadUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA3F99B92C92F6D30034BDA5 /* MarkOrderAsReadUseCaseTests.swift */; };
DA41043A2C247B6900E8456A /* POSOrderPreviewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4104392C247B6900E8456A /* POSOrderPreviewService.swift */; };
DA706BF92C8063B600E08A5B /* PushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575472802452185300A94C3C /* PushNotification.swift */; };
DA706BFA2C80642800E08A5B /* Dictionary+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57C5C9521B80E5400FF82B2 /* Dictionary+Woo.swift */; };
Expand Down Expand Up @@ -5511,6 +5512,7 @@
DA1D68C12C36F0980097859A /* PointOfSaleAssets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PointOfSaleAssets.swift; sourceTree = "<group>"; };
DA25ADDC2C86145E00AE81FE /* MarkOrderAsReadUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkOrderAsReadUseCase.swift; sourceTree = "<group>"; };
DA25ADDE2C87403900AE81FE /* PushNotificationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationTests.swift; sourceTree = "<group>"; };
DA3F99B92C92F6D30034BDA5 /* MarkOrderAsReadUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkOrderAsReadUseCaseTests.swift; sourceTree = "<group>"; };
DA4104392C247B6900E8456A /* POSOrderPreviewService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSOrderPreviewService.swift; sourceTree = "<group>"; };
DABF35262C11B426006AF826 /* PointOfSaleDashboardViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleDashboardViewModelTests.swift; sourceTree = "<group>"; };
DAD988C52C4A9CF9009DE9E3 /* CartItem+Order.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CartItem+Order.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -10561,6 +10563,7 @@
2631D4F929ED108400F13F20 /* WPComPlanNameSanitizer.swift */,
B98FF43F2AAA096200326D16 /* AddressWooTests.swift */,
023BD5852BFDCECF00A10D7B /* BetaFeaturesConfigurationViewModelTests.swift */,
DA3F99B92C92F6D30034BDA5 /* MarkOrderAsReadUseCaseTests.swift */,
);
path = Model;
sourceTree = "<group>";
Expand Down Expand Up @@ -16641,6 +16644,7 @@
EEB221A729B9B5B300662A12 /* CouponLineDetailsViewModelTests.swift in Sources */,
026D684B2A0E0A9600D8C22C /* LocalNotificationSchedulerTests.swift in Sources */,
02038C612AF222D600CD36D9 /* ConfigurableVariableBundleAttributePickerViewModelTests.swift in Sources */,
DA3F99BA2C92F6D30034BDA5 /* MarkOrderAsReadUseCaseTests.swift in Sources */,
26C0D1E32B460E5700F6EDA5 /* OrderNotificationViewModel.swift in Sources */,
86E40AED2B597DEC00990365 /* BlazeCampaignCreationCoordinatorTests.swift in Sources */,
20A3AFE12B0F750B0033AF2D /* MockInPersonPaymentsCashOnDeliveryToggleRowViewModel.swift in Sources */,
Expand Down
163 changes: 163 additions & 0 deletions WooCommerce/WooCommerceTests/Model/MarkOrderAsReadUseCaseTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import XCTest
import WooFoundation
@testable import Yosemite
@testable import Networking
@testable import Storage

final class MarkOrderAsReadUseCaseTests: XCTestCase {
private var dispatcher: Dispatcher!
private var network: MockNetwork!
private var storageManager: MockStorageManager!
private var storesManager: MockStoresManager!
private var viewStorage: StorageType {
return storageManager.viewStorage
}
private lazy var sampleNotes: [Yosemite.Note] = {
return try! mapNotes(from: "notifications-load-all")
}()

private func sampleNote(read: Bool) -> Yosemite.Note? {
return sampleNotes.first { note in
return note.read == read && note.meta.identifier(forKey: .order) != nil
}
}

override func setUp() {
super.setUp()
dispatcher = Dispatcher()
storageManager = MockStorageManager()
storesManager = MockStoresManager(sessionManager: .makeForTesting())
network = MockNetwork()

NotificationStore.resetSharedDerivedStorage()
}

override func tearDown() {
NotificationStore.resetSharedDerivedStorage()
super.tearDown()
}

private func setupStoreManagerReceivingNotificationActions(for note: Yosemite.Note, noteStore: NotificationStore) {
storesManager.whenReceivingAction(ofType: NotificationAction.self) { action in
switch action {
case let .synchronizeNotifications(onCompletion):
onCompletion(nil)
case let .synchronizeNotification(_, onCompletion):
onCompletion(note, nil)
case let .updateReadStatus(noteID, read, onCompletion):
noteStore.updateLocalNoteReadStatus(for: [noteID], read: read) {
onCompletion(nil)
}
default:
XCTFail("Unexpected action: \(action)")
}
}
}

@MainActor
func test_markOrderNoteAsReadIfNeeded_with_stores_unreadNote() async throws {
let unreadNote = try XCTUnwrap(sampleNote(read: false))
let orderID = try XCTUnwrap(unreadNote.meta.identifier(forKey: .order))

let noteStore = NotificationStore(dispatcher: dispatcher, storageManager: storageManager, network: network)

setupStoreManagerReceivingNotificationActions(for: unreadNote, noteStore: noteStore)

await withCheckedContinuation { continuation in
noteStore.updateLocalNotes(with: [unreadNote]) {
XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.Note.self), 1)
continuation.resume()
}
}

let result = await MarkOrderAsReadUseCase.markOrderNoteAsReadIfNeeded(stores: storesManager, noteID: unreadNote.noteID, orderID: orderID)
switch result {
case .success(let markedNote):
XCTAssertEqual(unreadNote.noteID, markedNote.noteID)
let storageNote = viewStorage.loadNotification(noteID: markedNote.noteID)
XCTAssertEqual(storageNote?.read, true)
case .failure(let error):
XCTFail(error.localizedDescription)
}
}

@MainActor
func test_markOrderNoteAsReadIfNeeded_with_stores_alreadyReadNote() async throws {
let readNote = try XCTUnwrap(sampleNote(read: true))
let orderID = try XCTUnwrap(readNote.meta.identifier(forKey: .order))

let noteStore = NotificationStore(dispatcher: dispatcher, storageManager: storageManager, network: network)

setupStoreManagerReceivingNotificationActions(for: readNote, noteStore: noteStore)

await withCheckedContinuation { continuation in
noteStore.updateLocalNotes(with: [readNote]) {
XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.Note.self), 1)
continuation.resume()
}
}

let result = await MarkOrderAsReadUseCase.markOrderNoteAsReadIfNeeded(stores: storesManager, noteID: readNote.noteID, orderID: orderID)
switch result {
case .success:
XCTFail("Note was already read, it should not be marked as read again.")
case .failure(let error):
if case MarkOrderAsReadUseCase.Error.noNeedToMarkAsRead = error {} else {
XCTFail("Got wrong error \(error.localizedDescription)")
}
}
}

@MainActor
func test_markOrderNoteAsReadIfNeeded_with_network_unreadNote() async throws {
let unreadNote = try XCTUnwrap(sampleNote(read: false))
let orderID = try XCTUnwrap(unreadNote.meta.identifier(forKey: .order))

network.simulateResponse(requestUrlSuffix: "notifications", filename: "notifications-load-all")
network.simulateResponse(requestUrlSuffix: "notifications/read", filename: "generic_success")

let result = await MarkOrderAsReadUseCase.markOrderNoteAsReadIfNeeded(network: network,
noteID: unreadNote.noteID,
orderID: orderID)

switch result {
case .success(let markedNoteID):
XCTAssertEqual(unreadNote.noteID, markedNoteID)
case .failure(let error):
XCTFail(error.localizedDescription)
}
}

@MainActor
func test_markOrderNoteAsReadIfNeeded_with_network_alreadyReadNote() async throws {
let readNote = try XCTUnwrap(sampleNote(read: true))
let orderID = try XCTUnwrap(readNote.meta.identifier(forKey: .order))

network.simulateResponse(requestUrlSuffix: "notifications", filename: "notifications-load-all")

let result = await MarkOrderAsReadUseCase.markOrderNoteAsReadIfNeeded(network: network,
noteID: readNote.noteID,
orderID: orderID)

switch result {
case .success:
XCTFail("Note was already read, it should not be marked as read again.")
case .failure(let error):
if case MarkOrderAsReadUseCase.Error.noNeedToMarkAsRead = error {} else {
XCTFail("Got wrong error \(error.localizedDescription)")
}
}
}
}

/// Private Methods.
///
private extension MarkOrderAsReadUseCaseTests {

/// Returns the NoteListMapper output upon receiving `filename` (Data Encoded)
///
func mapNotes(from filename: String) throws -> [Yosemite.Note] {
let response = Loader.contentsOf(filename)!
return try NoteListMapper().map(response: response)
}
}
4 changes: 2 additions & 2 deletions Yosemite/YosemiteTests/Stores/NotificationStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class NotificationStoreTests: XCTestCase {
XCTAssertEqual(viewStorage.countObjects(ofType: Storage.Note.self), 0)
let action = NotificationAction.synchronizeNotifications() { (error) in
XCTAssertNil(error)
XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.Note.self), 44)
XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.Note.self), MockNetwork.notificationLoadAllJSONCount)

if let note = self.viewStorage.loadNotification(noteID: 100036, noteHash: 987654)?.toReadOnly() {
// Plain Fields
Expand Down Expand Up @@ -111,7 +111,7 @@ class NotificationStoreTests: XCTestCase {
/// Initial Sync
///
let initialSyncAction = NotificationAction.synchronizeNotifications() { (error) in
XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.Note.self), 44)
XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.Note.self), MockNetwork.notificationLoadAllJSONCount)
notificationStore.onAction(nestedSyncAction)
}

Expand Down