zashi-ios-wallet-private/secantTests/RecoveryPhraseValidationTests/RecoveryPhraseValidationTes...

692 lines
23 KiB
Swift

//
// RecoveryPhraseValidationTests.swift
// secantTests
//
// Created by Francisco Gindre on 10/29/21.
//
// swiftlint:disable type_body_length
import XCTest
import ComposableArchitecture
@testable import secant_testnet
class RecoveryPhraseValidationTests: XCTestCase {
static let testScheduler = DispatchQueue.test
func testPickWordsFromMissingIndices() throws {
let words = [
"bring", "salute", "thank",
"require", "spirit", "toe",
// second chunk
"boil", "hill", "casino",
"trophy", "drink", "frown",
// third chunk
"bird", "grit", "close",
"morning", "bind", "cancel",
// Fourth chunk
"daughter", "salon", "quit",
"pizza", "just", "garlic"
]
let phrase = RecoveryPhrase(words: words.map { $0.redacted })
let indices = [1, 0, 5, 3]
let expected = [
"salute".redacted,
"boil".redacted,
"cancel".redacted,
"pizza".redacted
].map({ PhraseChip.Kind.unassigned(word: $0) })
let result = phrase.words(fromMissingIndices: indices)
XCTAssertEqual(expected, result)
}
func testWhenInInitialStepChipIsDraggedIntoGroup1FollowingStepIsIncomplete() {
let words = [
"bring", "salute", "thank",
"require", "spirit", "toe",
// second chunk
"boil", "hill", "casino",
"trophy", "drink", "frown",
// third chunk
"bird", "grit", "close",
"morning", "bind", "cancel",
// Fourth chunk
"daughter", "salon", "quit",
"pizza", "just", "garlic"
]
let missingIndices = [1, 0, 5, 3]
let phrase = RecoveryPhrase(words: words.map { $0.redacted })
let missingWordChips: [PhraseChip.Kind] = [
"salute".redacted,
"boil".redacted,
"cancel".redacted,
"pizza".redacted
].map({ PhraseChip.Kind.unassigned(word: $0) })
let initialStep = RecoveryPhraseValidationFlowReducer.State(
phrase: phrase,
missingIndices: missingIndices,
missingWordChips: missingWordChips,
validationWords: []
)
let store = TestStore(
initialState: initialStep,
reducer: RecoveryPhraseValidationFlowReducer()
)
let expectedMissingChips = [
PhraseChip.Kind.empty,
PhraseChip.Kind.unassigned(word: "boil".redacted),
PhraseChip.Kind.unassigned(word: "cancel".redacted),
PhraseChip.Kind.unassigned(word: "pizza".redacted)
]
let expectedValidationWords = [ValidationWord(groupIndex: 1, word: "salute".redacted)]
store.send(.move(wordChip: .unassigned(word: "salute".redacted), intoGroup: 1)) {
$0.validationWords = expectedValidationWords
$0.missingWordChips = expectedMissingChips
XCTAssertFalse($0.isComplete)
}
}
func testWhenInInitialStepChipIsDraggedIntoGroup0FollowingStepIsIncompleteNextStateIsIncomplete() {
let words = [
"bring", "salute", "thank",
"require", "spirit", "toe",
// second chunk
"boil", "hill", "casino",
"trophy", "drink", "frown",
// third chunk
"bird", "grit", "close",
"morning", "bind", "cancel",
// Fourth chunk
"daughter", "salon", "quit",
"pizza", "just", "garlic"
]
let missingIndices = [1, 0, 5, 3]
let phrase = RecoveryPhrase(words: words.map { $0.redacted })
let missingWordChips = [
"salute".redacted,
"boil".redacted,
"cancel".redacted,
"pizza".redacted
].map({ PhraseChip.Kind.unassigned(word: $0) })
let initialStep = RecoveryPhraseValidationFlowReducer.State.initial(
phrase: phrase,
missingIndices: missingIndices,
missingWordsChips: missingWordChips
)
let store = TestStore(
initialState: initialStep,
reducer: RecoveryPhraseValidationFlowReducer()
)
let expectedMissingChips = [
PhraseChip.Kind.unassigned(word: "salute".redacted),
PhraseChip.Kind.unassigned(word: "boil".redacted),
PhraseChip.Kind.unassigned(word: "cancel".redacted),
PhraseChip.Kind.empty
]
let expectedValidationWords = [ValidationWord(groupIndex: 0, word: "pizza".redacted)]
store.send(.move(wordChip: missingWordChips[3], intoGroup: 0)) {
$0.missingWordChips = expectedMissingChips
$0.validationWords = expectedValidationWords
XCTAssertFalse($0.isComplete)
}
}
func testWhenInIncompleteWith2CompletionsAndAChipIsDroppedInGroup3NextStateIsIncomplete() {
let words = [
"bring", "salute", "thank",
"require", "spirit", "toe",
// second chunk
"boil", "hill", "casino",
"trophy", "drink", "frown",
// third chunk
"bird", "grit", "close",
"morning", "bind", "cancel",
// Fourth chunk
"daughter", "salon", "quit",
"pizza", "just", "garlic"
]
let missingIndices = [1, 0, 5, 3]
let phrase = RecoveryPhrase(words: words.map { $0.redacted })
let currentStep = RecoveryPhraseValidationFlowReducer.State(
phrase: phrase,
missingIndices: missingIndices,
missingWordChips: [
PhraseChip.Kind.empty,
PhraseChip.Kind.unassigned(word: "boil".redacted),
PhraseChip.Kind.unassigned(word: "cancel".redacted),
PhraseChip.Kind.unassigned(word: "pizza".redacted)
],
validationWords: [ValidationWord(groupIndex: 0, word: "salute".redacted)]
)
let store = TestStore(
initialState: currentStep,
reducer: RecoveryPhraseValidationFlowReducer()
)
let expectedMissingWordChips = [
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.unassigned(word: "cancel".redacted),
PhraseChip.Kind.unassigned(word: "pizza".redacted)
]
let expectedValidationWords = [
ValidationWord(groupIndex: 0, word: "salute".redacted),
ValidationWord(groupIndex: 1, word: "boil".redacted)
]
store.send(.move(wordChip: PhraseChip.Kind.unassigned(word: "boil".redacted), intoGroup: 1)) {
$0.missingWordChips = expectedMissingWordChips
$0.validationWords = expectedValidationWords
XCTAssertFalse($0.isComplete)
}
}
func testWhenInIncompleteWith2CompletionsAndAChipIsDroppedInGroup2NextStateIsIncomplete() {
let words = [
"bring", "salute", "thank",
"require", "spirit", "toe",
// second chunk
"boil", "hill", "casino",
"trophy", "drink", "frown",
// third chunk
"bird", "grit", "close",
"morning", "bind", "cancel",
// Fourth chunk
"daughter", "salon", "quit",
"pizza", "just", "garlic"
]
let missingIndices = [1, 0, 5, 3]
let phrase = RecoveryPhrase(words: words.map { $0.redacted })
let currentStep = RecoveryPhraseValidationFlowReducer.State(
phrase: phrase,
missingIndices: missingIndices,
missingWordChips: [
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.unassigned(word: "cancel".redacted),
PhraseChip.Kind.unassigned(word: "pizza".redacted)
],
validationWords: [
ValidationWord(groupIndex: 0, word: "salute".redacted),
ValidationWord(groupIndex: 1, word: "boil".redacted)
]
)
let store = TestStore(
initialState: currentStep,
reducer: RecoveryPhraseValidationFlowReducer()
)
let expectedMissingWordChips = [
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.unassigned(word: "pizza".redacted)
]
let expectedValidationWords = [
ValidationWord(groupIndex: 0, word: "salute".redacted),
ValidationWord(groupIndex: 1, word: "boil".redacted),
ValidationWord(groupIndex: 2, word: "cancel".redacted)
]
store.send(.move(wordChip: PhraseChip.Kind.unassigned(word: "cancel".redacted), intoGroup: 2)) {
$0.missingWordChips = expectedMissingWordChips
$0.validationWords = expectedValidationWords
XCTAssertFalse($0.isComplete)
}
}
func testWhenInIncompleteWith3CompletionsAndAChipIsDroppedInGroup3NextStateIsComplete() {
let words = [
"bring", "salute", "thank",
"require", "spirit", "toe",
// second chunk
"boil", "hill", "casino",
"trophy", "drink", "frown",
// third chunk
"bird", "grit", "close",
"morning", "bind", "cancel",
// Fourth chunk
"daughter", "salon", "quit",
"pizza", "just", "garlic"
]
let missingIndices = [1, 0, 5, 3]
let phrase = RecoveryPhrase(words: words.map { $0.redacted })
let currentStep = RecoveryPhraseValidationFlowReducer.State(
phrase: phrase,
missingIndices: missingIndices,
missingWordChips: [
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.unassigned(word: "pizza".redacted)
],
validationWords: [
ValidationWord(groupIndex: 0, word: "salute".redacted),
ValidationWord(groupIndex: 1, word: "boil".redacted),
ValidationWord(groupIndex: 2, word: "cancel".redacted)
]
)
let store = TestStore(
initialState: currentStep,
reducer: RecoveryPhraseValidationFlowReducer()
) {
$0.mainQueue = Self.testScheduler.eraseToAnyScheduler()
}
let expectedMissingWordChips = [
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.empty
]
let expectedValidationWords = [
ValidationWord(groupIndex: 0, word: "salute".redacted),
ValidationWord(groupIndex: 1, word: "boil".redacted),
ValidationWord(groupIndex: 2, word: "cancel".redacted),
ValidationWord(groupIndex: 3, word: "pizza".redacted)
]
store.send(.move(wordChip: PhraseChip.Kind.unassigned(word: "pizza".redacted), intoGroup: 3)) {
$0.missingWordChips = expectedMissingWordChips
$0.validationWords = expectedValidationWords
XCTAssertTrue($0.isComplete)
XCTAssertTrue($0.isValid)
}
Self.testScheduler.advance(by: 2)
store.receive(.succeed) {
XCTAssertTrue($0.isComplete)
$0.destination = .success
}
}
func testWhenInIncompleteWith3CompletionsAndAChipIsDroppedInGroup3NextStateIsFailure() {
let words = [
"bring", "salute", "thank",
"require", "spirit", "toe",
// second chunk
"boil", "hill", "casino",
"trophy", "drink", "frown",
// third chunk
"bird", "grit", "close",
"morning", "bind", "cancel",
// Fourth chunk
"daughter", "salon", "quit",
"pizza", "just", "garlic"
]
let missingIndices = [1, 0, 5, 3]
let phrase = RecoveryPhrase(words: words.map { $0.redacted })
let currentStep = RecoveryPhraseValidationFlowReducer.State(
phrase: phrase,
missingIndices: missingIndices,
missingWordChips: [
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.unassigned(word: "pizza".redacted)
],
validationWords: [
ValidationWord(groupIndex: 0, word: "salute".redacted),
ValidationWord(groupIndex: 2, word: "boil".redacted),
ValidationWord(groupIndex: 1, word: "cancel".redacted)
]
)
let store = TestStore(
initialState: currentStep,
reducer: RecoveryPhraseValidationFlowReducer()
) { dependencies in
dependencies.feedbackGenerator = .noOp
dependencies.mainQueue = Self.testScheduler.eraseToAnyScheduler()
}
let expectedMissingWordChips = [
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.empty
]
let expectedValidationWords = [
ValidationWord(groupIndex: 0, word: "salute".redacted),
ValidationWord(groupIndex: 2, word: "boil".redacted),
ValidationWord(groupIndex: 1, word: "cancel".redacted),
ValidationWord(groupIndex: 3, word: "pizza".redacted)
]
store.send(.move(wordChip: PhraseChip.Kind.unassigned(word: "pizza".redacted), intoGroup: 3)) {
$0.missingWordChips = expectedMissingWordChips
$0.validationWords = expectedValidationWords
XCTAssertTrue($0.isComplete)
}
Self.testScheduler.advance(by: 2)
store.receive(.failureFeedback)
store.receive(.fail) {
$0.destination = .failure
XCTAssertFalse($0.isValid)
}
}
func testWhenAWordGroupDoesNotHaveACompletionItHasAnEmptyChipInTheGivenMissingIndex() {
let words = [
"bring", "salute", "thank",
"require", "spirit", "toe",
// second chunk
"boil", "hill", "casino",
"trophy", "drink", "frown",
// third chunk
"bird", "grit", "close",
"morning", "bind", "cancel",
// Fourth chunk
"daughter", "salon", "quit",
"pizza", "just", "garlic"
]
let missingIndices = [1, 0, 5, 3]
let phrase = RecoveryPhrase(words: words.map { $0.redacted })
let currentStep = RecoveryPhraseValidationFlowReducer.State(
phrase: phrase,
missingIndices: missingIndices,
missingWordChips: [
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.unassigned(word: "pizza".redacted)
],
validationWords: [
ValidationWord(groupIndex: 1, word: "boil".redacted),
ValidationWord(groupIndex: 2, word: "cancel".redacted)
]
)
let result = currentStep.wordsChips(
for: 0,
groupSize: 6,
from: phrase.toGroups()[0]
)
let expected = [
PhraseChip.Kind.ordered(position: 1, word: "bring".redacted),
.empty,
.ordered(position: 3, word: "thank".redacted),
.ordered(position: 4, word: "require".redacted),
.ordered(position: 5, word: "spirit".redacted),
.ordered(position: 6, word: "toe".redacted)
]
XCTAssertEqual(expected, result)
}
func testWhenAWordGroupHasACompletionItHasABlueChipWithTheCompletedWordInTheGivenMissingIndex() {
let words = [
"bring", "salute", "thank",
"require", "spirit", "toe",
// second chunk
"boil", "hill", "casino",
"trophy", "drink", "frown",
// third chunk
"bird", "grit", "close",
"morning", "bind", "cancel",
// Fourth chunk
"daughter", "salon", "quit",
"pizza", "just", "garlic"
]
let missingIndices = [1, 0, 5, 3]
let phrase = RecoveryPhrase(words: words.map { $0.redacted })
let currentStep = RecoveryPhraseValidationFlowReducer.State(
phrase: phrase,
missingIndices: missingIndices,
missingWordChips: [
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.unassigned(word: "pizza".redacted)
],
validationWords: [
ValidationWord(groupIndex: 0, word: "salute".redacted),
ValidationWord(groupIndex: 1, word: "boil".redacted),
ValidationWord(groupIndex: 2, word: "cancel".redacted)
]
)
let result = currentStep.wordsChips(
for: 0,
groupSize: 6,
from: phrase.toGroups()[0]
)
let expected = [
PhraseChip.Kind.ordered(position: 1, word: "bring".redacted),
.unassigned(word: "salute".redacted),
.ordered(position: 3, word: "thank".redacted),
.ordered(position: 4, word: "require".redacted),
.ordered(position: 5, word: "spirit".redacted),
.ordered(position: 6, word: "toe".redacted)
]
XCTAssertEqual(expected, result)
}
func testWhenRecoveryPhraseValidationStateIsNotCompleteResultingPhraseIsNil() {
let words = [
"bring", "salute", "thank",
"require", "spirit", "toe",
// second chunk
"boil", "hill", "casino",
"trophy", "drink", "frown",
// third chunk
"bird", "grit", "close",
"morning", "bind", "cancel",
// Fourth chunk
"daughter", "salon", "quit",
"pizza", "just", "garlic"
]
let missingIndices = [1, 0, 5, 3]
let phrase = RecoveryPhrase(words: words.map { $0.redacted })
let currentStep = RecoveryPhraseValidationFlowReducer.State(
phrase: phrase,
missingIndices: missingIndices,
missingWordChips: [
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.unassigned(word: "pizza".redacted)
],
validationWords: [
ValidationWord(groupIndex: 0, word: "salute".redacted),
ValidationWord(groupIndex: 1, word: "boil".redacted),
ValidationWord(groupIndex: 2, word: "cancel".redacted)
]
)
XCTAssertNil(currentStep.resultingPhrase)
}
func testRecoveryPhraseValidationStateIsNotCompleteAndNotValidWhenNotCompleted() {
let words = [
"bring", "salute", "thank",
"require", "spirit", "toe",
// second chunk
"boil", "hill", "casino",
"trophy", "drink", "frown",
// third chunk
"bird", "grit", "close",
"morning", "bind", "cancel",
// Fourth chunk
"daughter", "salon", "quit",
"pizza", "just", "garlic"
]
let missingIndices = [1, 0, 5, 3]
let phrase = RecoveryPhrase(words: words.map { $0.redacted })
let currentStep = RecoveryPhraseValidationFlowReducer.State(
phrase: phrase,
missingIndices: missingIndices,
missingWordChips: [
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.empty,
PhraseChip.Kind.unassigned(word: "pizza".redacted)
],
validationWords: [
ValidationWord(groupIndex: 0, word: "salute".redacted),
ValidationWord(groupIndex: 1, word: "boil".redacted),
ValidationWord(groupIndex: 2, word: "cancel".redacted)
]
)
XCTAssertFalse(currentStep.isComplete)
XCTAssertFalse(currentStep.isValid)
}
func testCreateResultPhraseFromCompletion() {
let words = [
"bring", "salute", "thank",
"require", "spirit", "toe",
// second chunk
"boil", "hill", "casino",
"trophy", "drink", "frown",
// third chunk
"bird", "grit", "close",
"morning", "bind", "cancel",
// Fourth chunk
"daughter", "salon", "quit",
"pizza", "just", "garlic"
]
let missingIndices = [1, 0, 5, 3]
let phrase = RecoveryPhrase(words: words.map { $0.redacted })
let completion = [
ValidationWord(groupIndex: 0, word: "salute".redacted),
ValidationWord(groupIndex: 1, word: "boil".redacted),
ValidationWord(groupIndex: 2, word: "cancel".redacted),
ValidationWord(groupIndex: 3, word: "pizza".redacted)
]
let result = RecoveryPhraseValidationFlowReducer.State(
phrase: phrase,
missingIndices: missingIndices,
missingWordChips: phrase.words(fromMissingIndices: missingIndices),
validationWords: completion,
destination: nil
)
XCTAssertTrue(result.isValid)
XCTAssertTrue(result.isComplete)
XCTAssertEqual(words.map { $0.redacted }, result.resultingPhrase)
}
func testCreateResultPhraseInvalidPhraseFromCompletion() {
let words = [
"bring", "salute", "thank",
"require", "spirit", "toe",
// second chunk
"boil", "hill", "casino",
"trophy", "drink", "frown",
// third chunk
"bird", "grit", "close",
"morning", "bind", "cancel",
// Fourth chunk
"daughter", "salon", "quit",
"pizza", "just", "garlic"
]
let missingIndices = [1, 0, 5, 3]
let phrase = RecoveryPhrase(words: words.map { $0.redacted })
let completion = [
ValidationWord(groupIndex: 3, word: "salute".redacted),
ValidationWord(groupIndex: 1, word: "boil".redacted),
ValidationWord(groupIndex: 0, word: "cancel".redacted),
ValidationWord(groupIndex: 2, word: "pizza".redacted)
]
let result = RecoveryPhraseValidationFlowReducer.State(
phrase: phrase,
missingIndices: missingIndices,
missingWordChips: phrase.words(fromMissingIndices: missingIndices),
validationWords: completion,
destination: nil
)
XCTAssertFalse(result.isValid)
XCTAssertTrue(result.isComplete)
XCTAssertNotEqual(words.map { $0.redacted }, result.resultingPhrase)
}
}
extension RecoveryPhraseValidationFlowReducer.State {
static func initial(
phrase: RecoveryPhrase,
missingIndices: [Int],
missingWordsChips: [PhraseChip.Kind]
) -> Self {
RecoveryPhraseValidationFlowReducer.State(
phrase: phrase,
missingIndices: missingIndices,
missingWordChips: missingWordsChips,
validationWords: []
)
}
}