Skip to content

Commit

Permalink
Tests migrated from macOS for migrated classes
Browse files Browse the repository at this point in the history
  • Loading branch information
amddg44 committed Jan 23, 2025
1 parent 123bba5 commit aa1d559
Show file tree
Hide file tree
Showing 5 changed files with 595 additions and 0 deletions.
133 changes: 133 additions & 0 deletions Tests/BrowserServicesKitTests/DataImport/CSVImporterTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//
// CSVImporterTests.swift
//
// Copyright © 2021 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation
import XCTest
@testable import BrowserServicesKit
import SecureStorage

class CSVImporterTests: XCTestCase {

let temporaryFileCreator = TemporaryFileCreator()

override func tearDown() {
super.tearDown()
temporaryFileCreator.deleteCreatedTemporaryFiles()
}

func testWhenImportingCSVFileWithHeader_ThenHeaderRowIsExcluded() {
let csvFileContents = """
title,url,username,password
Some Title,duck.com,username,p4ssw0rd
"""

let logins = CSVImporter.extractLogins(from: csvFileContents)
XCTAssertEqual(logins, [ImportedLoginCredential(title: "Some Title", url: "duck.com", username: "username", password: "p4ssw0rd")])
}

func testWhenImportingCSVFileWithHeader_AndHeaderHasBitwardenFormat_ThenHeaderRowIsExcluded() {
let csvFileContents = """
name,login_uri,login_username,login_password
Some Title,duck.com,username,p4ssw0rd
"""

let logins = CSVImporter.extractLogins(from: csvFileContents)
XCTAssertEqual(logins, [ImportedLoginCredential(title: "Some Title", url: "duck.com", username: "username", password: "p4ssw0rd")])
}

func testWhenImportingCSVFileWithHeader_ThenHeaderColumnPositionsAreRespected() {
let csvFileContents = """
Password,Title,Username,Url
p4ssw0rd,"Some Title",username,duck.com
"""

let logins = CSVImporter.extractLogins(from: csvFileContents)
XCTAssertEqual(logins, [ImportedLoginCredential(title: "Some Title", url: "duck.com", username: "username", password: "p4ssw0rd")])
}

func testWhenImportingCSVFileWithoutHeader_ThenNoRowsAreExcluded() {
let csvFileContents = """
Some Title,duck.com,username,p4ssw0rd
"""

let logins = CSVImporter.extractLogins(from: csvFileContents)
XCTAssertEqual(logins, [ImportedLoginCredential(title: "Some Title", url: "duck.com", username: "username", password: "p4ssw0rd")])
}

func testWhenImportingCSVDataFromTheFileSystem_AndNoTitleIsIncluded_ThenLoginCredentialsAreImported() async {
let mockLoginImporter = MockLoginImporter()
let file = "https://example.com/,username,password"
let savedFileURL = temporaryFileCreator.persist(fileContents: file.data(using: .utf8)!, named: "test.csv")!
let csvImporter = CSVImporter(fileURL: savedFileURL, loginImporter: mockLoginImporter, defaultColumnPositions: nil, reporter: MockSecureVaultReporting())

let result = await csvImporter.importData(types: [.passwords]).task.value

XCTAssertEqual(result, [.passwords: .success(.init(successful: 1, duplicate: 0, failed: 0))])
}

func testWhenImportingCSVDataFromTheFileSystem_AndTitleIsIncluded_ThenLoginCredentialsAreImported() async {
let mockLoginImporter = MockLoginImporter()
let file = "title,https://example.com/,username,password"
let savedFileURL = temporaryFileCreator.persist(fileContents: file.data(using: .utf8)!, named: "test.csv")!
let csvImporter = CSVImporter(fileURL: savedFileURL, loginImporter: mockLoginImporter, defaultColumnPositions: nil, reporter: MockSecureVaultReporting())

let result = await csvImporter.importData(types: [.passwords]).task.value

XCTAssertEqual(result, [.passwords: .success(.init(successful: 1, duplicate: 0, failed: 0))])
}

func testWhenInferringColumnPostions_AndColumnsAreValid_AndTitleIsIncluded_ThenPositionsAreCalculated() {
let csvValues = ["url", "username", "password", "title"]
let inferred = CSVImporter.ColumnPositions(csvValues: csvValues)

XCTAssertEqual(inferred?.urlIndex, 0)
XCTAssertEqual(inferred?.usernameIndex, 1)
XCTAssertEqual(inferred?.passwordIndex, 2)
XCTAssertEqual(inferred?.titleIndex, 3)
}

func testWhenInferringColumnPostions_AndColumnsAreValid_AndTitleIsNotIncluded_ThenPositionsAreCalculated() {
let csvValues = ["url", "username", "password"]
let inferred = CSVImporter.ColumnPositions(csvValues: csvValues)

XCTAssertEqual(inferred?.urlIndex, 0)
XCTAssertEqual(inferred?.usernameIndex, 1)
XCTAssertEqual(inferred?.passwordIndex, 2)
XCTAssertNil(inferred?.titleIndex)
}

func testWhenInferringColumnPostions_AndColumnsAreInvalidThenPositionsAreCalculated() {
let csvValues = ["url", "username", "title"] // `password` is required, this test verifies that the inference fails when it's missing
let inferred = CSVImporter.ColumnPositions(csvValues: csvValues)

XCTAssertNil(inferred)
}

}

extension CSVImporter.ColumnPositions {

init?(csvValues: [String]) {
self.init(csv: [csvValues, Array(repeating: "", count: csvValues.count)])
}

}

private class MockSecureVaultReporting: SecureVaultReporting {
func secureVaultError(_ error: SecureStorage.SecureStorageError) {}
}
226 changes: 226 additions & 0 deletions Tests/BrowserServicesKitTests/DataImport/CSVParserTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
//
// CSVParserTests.swift
//
// Copyright © 2021 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation
import XCTest
@testable import BrowserServicesKit

final class CSVParserTests: XCTestCase {

func testWhenParsingMultipleRowsThenMultipleArraysAreReturned() throws {
let string = """
line 1
line 2
"""
let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [["line 1"], ["line 2"]])
}

func testControlCharactersAreIgnored() throws {
let string = """
\u{FEFF}line\u{10} 1\u{10}
line 2\u{FEFF}
"""
let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [["line 1"], ["line 2"]])
}

func testWhenParsingRowsWithVariableNumbersOfEntriesThenParsingSucceeds() throws {
let string = """
one
two;three;
four;five;six
"""
let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [["one"], ["two", "three", ""], ["four", "five", "six"]])
}

func testWhenParsingRowsSurroundedByQuotesThenQuotesAreRemoved() throws {
let string = """
"url","username", "password"
""" + " "

let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [["url", "username", "password"]])
}

func testWhenParsingMalformedCsvWithExtraQuote_ParserAddsItToOutput() throws {
let string = #"""
"url","user"name","password",""
"a.b.com/usdf","[email protected]","\"mypass\wrd\",""
"""#

let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [
["url", #"user"name"#, "password", ""],
["a.b.com/usdf", "[email protected]", #""mypass\wrd\"#, ""],
])
}

func testWhenParsingRowsWithDoubleQuoteEscapedQuoteThenQuoteIsUnescaped() throws {
let string = #"""
"url","username","password\""with""quotes\""and/slash""\"
"""#

let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [["url", "username", #"password\"with"quotes\"and/slash"\"#]])
}

func testWhenParsingRowsWithBackslashEscapedQuoteThenQuoteIsUnescaped() throws {
let string = #"""
"\\","\"password\"with","quotes\","\",\",
"""#
let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [[#"\\"#, #""password"with"#, #"quotes\"#, #"",\"#, ""]])
}

func testWhenParsingRowsWithBackslashEscapedQuoteThenQuoteIsUnescaped2() throws {
let string = #"""
"\"", "\"\"","\\","\"password\"with","quotes\","\",\",
"""#
let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed.map({ $0.map { $0.replacingOccurrences(of: "\\", with: "|").replacingOccurrences(of: "\"", with: "") } }), [["\"", "\"\"", #"\\"#, #""password"with"#, #"quotes\"#, #"",\"#, ""].map { $0.replacingOccurrences(of: "\\", with: "|").replacingOccurrences(of: "\"", with: "") }])
}

func testWhenParsingRowsWithUnescapedTitleAndEscapedQuoteAndEmojiThenQuoteIsUnescaped() throws {
let string = #"""
Title,Url,Username,Password,OTPAuth,Notes
"A",,"",,,"It’s \\"you! 🖐 Select Edit to fill in more details, like your address and contact information.\",
"""#

let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [
["Title", "Url", "Username", "Password", "OTPAuth", "Notes"],
["A", "", "", "", "", #"It’s \"you! 🖐 Select Edit to fill in more details, like your address and contact information.\"#, ""]
])
}

func testWhenParsingRowsWithEscapedQuotesAndLineBreaksQuotesUnescapedAndLinebreaksParsed() throws {
let string = #"""
Title,Url,Username,Password,OTPAuth,Notes
"А",,"",,,"It's\",
B,,,,you! 🖐 se\" ect Edit to fill in more details, like your address and contact
information.",
"""#

let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [
["Title", "Url", "Username", "Password", "OTPAuth", "Notes"],
["А", "", "", "", "", "It's\",\nB,,,,you! 🖐 se\" ect Edit to fill in more details, like your address and contact\ninformation.", ""]
])
}

func testWhenParsingQuotedRowsContainingCommasThenTheyAreTreatedAsOneColumnEntry() throws {
let string = """
"url","username","password,with,commas"
"""

let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [["url", "username", "password,with,commas"]])
}

func testWhenSingleValueWithQuotedEscapedQuoteThenQuoteIsUnescaped() throws {
let string = #"""
"\""
"""#

let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [["\""]])
}

func testSingleEnquotedBackslashValueIsParsedAsBackslash() throws {
let string = #"""
"\"
"""#

let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [[#"\"#]])
}

func testBackslashValueWithDoubleQuoteIsParsedAsBackslashWithQuote() throws {
let string = #"""
\""
"""#

let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [[#"\""#]])
}

func testEnquotedBackslashValueWithDoubleQuoteIsParsedAsBackslashWithQuote() throws {
let string = #"""
"\"""
"""#

let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [[#"\""#]])
}

func testEscapedDoubleQuote() throws {
let string = #"""
""""
"""#

let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [["\""]])
}

func testWhenValueIsDoubleQuotedThenQuotesAreUnescaped() throws {
let string = #"""
""hello!""
"""#

let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [["\"hello!\""]])
}

func testWhenExpressionIsParsableEitherAsDoubleQuoteEscapedOrBackslashEscapedThenEqualFieldsNumberIsPreferred() throws {
let string = #"""
1,2,3,4,5,6,
"\",b,c,d,e,f,
asdf,\"",hi there!,4,5,6,
a,b,c,d,e,f,
"""#

let parsed = try CSVParser().parse(string: string)

XCTAssertEqual(parsed, [
["1", "2", "3", "4", "5", "6", ""],
["\\", "b", "c", "d", "e", "f", ""],
["asdf", #"\""#, "hi there!", "4", "5", "6", ""],
["a", "b", "c", "d", "e", "f", ""],
])
}

}
33 changes: 33 additions & 0 deletions Tests/BrowserServicesKitTests/DataImport/MockLoginImporter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// MockLoginImporter.swift
//
// Copyright © 2025 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation
@testable import BrowserServicesKit
import SecureStorage

final public class MockLoginImporter: LoginImporter {
var importedLogins: DataImportSummary?

public func importLogins(_ logins: [BrowserServicesKit.ImportedLoginCredential], reporter: SecureVaultReporting, progressCallback: @escaping (Int) throws -> Void) throws -> DataImport.DataTypeSummary {
let summary = DataImport.DataTypeSummary(successful: logins.count, duplicate: 0, failed: 0)

self.importedLogins = [.passwords: .success(summary)]
return summary
}

}
Loading

0 comments on commit aa1d559

Please sign in to comment.