How do I create system integration tests using Firebase and Swift/Xcode? - swift

I want to create tests for user account creation, so I can confirm my app is uploading the correct data, but I'm struggling to find the right way to do it. So far I've been unsuccessful in writing a test that works. This error prevents me from getting any further:
caught error: "An error occurred when accessing the keychain. The NSLocalizedFailureReasonErrorKey field in the NSError.userInfo dictionary will contain more information about the error encountered"
I've not written tests in Xcode before, and I've had lots of trouble finding guides on this sort of issue. Here is my test code:
import XCTest
import Firebase
import FirebaseFirestoreSwift
import SwiftUI
#testable import Baseline
class AccountTests: XCTestCase {
var baselineApp: BaselineApp!
override func setUpWithError() throws {
baselineApp = BaselineApp()
}
override func tearDownWithError() throws {
async {
let idString = baselineApp.authenticationState.loggedInUser?.uid
if let idString = idString {
try await baselineApp.authenticationState.deleteUser(id: idString)
}
baselineApp.authenticationState.signout()
}
}
func testAccountCreation() async throws {
let firstName = "Test"
let lastName = "Xcode"
let email = "testdummy5684+XCtest#gmail.com"
let password = "testingForXcode1234"
let gender = UserDocument.Gender.male
let birthdate = Timestamp(date: Date())
let suggestedRank = 0
let image = UIImage(named: "social")?.jpegData(compressionQuality: 1.0)
try await baselineApp.authenticationState.signupNewUser(email: email, password: password, firstName: firstName, lastName: lastName, gender: gender, jpegImageData: image, suggestedRank: suggestedRank, birthdate: birthdate)
let foundUserResult = baselineApp.authenticationState.loggedInUser
let foundUserDocumentResult = baselineApp.authenticationState.loggedInUserDocument
let foundUser = try XCTUnwrap(foundUserResult)
let foundUserDocument = try XCTUnwrap(foundUserDocumentResult)
XCTAssertNotNil(foundUserDocument, "Failed to get user document")
XCTAssertEqual(firstName, foundUserDocument.firstName, "Tried \(firstName), but got \(foundUserDocument.firstName)")
}
}
I'm creating an instance of my app where my #main is, because I have observable objects instantiated there, alongside Firebase's config settings. I have keychain sharing enabled, and my app works otherwise (like logging into a pre-existing account).
I'm running Xcode 13.0 beta (13A5155e) and targeting iOS 15.

Related

Xcode Trace/BPT trap: 5

Situation
Hi there,
I am developing an iOS app and while building my project I run into the following error message:
Error: Trace/BPT trap: 5
I didn't find anything online to fix this problem, so I wanted to know, if anyone here might be able to help.
I also had issues with Cocoapods and my Silicon Mac, so I want to list my steps I've tried fixing:
Setup
M1 MacBook Pro, macOS 11.1
XCode Version 12.4
Cocoapods with Pods for Firebase Swift, Auth, Firestore and Storage
Steps I tried fixing
cmd + shift + k for cleaning the build folder
closing XCode and opening Terminal using Rosetta
delete ~/Library/Developer/Xcode/Derived Data - Folder
pod deintegrate in project directory
delete Podfile.lock, app.xcworkspace, Pods directory
pod install
in app and pods projects build settings setting Excluded Architectures for any iOS Simulator SDK to arm64
setting Build Active Architecture Only to yes
convert Pods Project to Swift 5
build Pods Project
build app project
And then the following error occurs:
Log
Log enty from Merge swiftmodule (x86_64):
https://pastebin.com/MiSKGxB7
(Log way to long, exceeds character limit).
Code
As the error somewhere tells, it occured while trying to serialize the class BaseViewModel, here's the code from the Base.swift file I wrote containing that class:
import SwiftUI
import Firebase
import FirebaseFirestore
import Combine
protocol BaseModel: Identifiable, Codable {
var id: String? { get set }
var collection: String { get }
init()
}
class BaseViewModel<T: BaseModel>: ObservableObject, Identifiable, Equatable {
#Published var model: T
var id: String {
didSet {
self.model.id = id
}
}
var cancellables = [AnyCancellable]()
private var db = Firestore.firestore()
required init(){
let model = T.init()
self.model = model
self.id = model.id ?? UUID().uuidString
}
required init(id: String) {
var model = T.init()
model.id = id
self.model = model
self.id = id
}
init(model: T) {
self.model = model
self.id = model.id ?? UUID().uuidString
}
static func ==(lhs: BaseViewModel<T>, rhs: BaseViewModel<T>) -> Bool {
lhs.model.id == rhs.model.id
}
func load(completion: #escaping (Bool) -> Void = {finished in}){
if let id = model.id {
self.id = id
db.collection(model.collection).document(id).getDocument { docSnapshot, error in
guard let doc = docSnapshot else {
print("Error fetching document: \(error!)")
return
}
do {
guard let data = try doc.data(as: T.self) else {
print("Document empty \(type(of: self.model)) with id \(id)")
return
}
self.model = data
self.loadSubData {finished in
if finished{
completion(true)
}
}
} catch {
print(error.localizedDescription)
}
}
}
}
func loadSubData(completion: #escaping(Bool) -> Void = {finished in}) {
fatalError("Must be overridden!")
}
func loadDataByIDs<T, S>(from list: [String], appender: #escaping (T) -> Void) where T: BaseViewModel<S>, S: BaseModel {
for id in list {
let viewModel = T.init(id: id)
viewModel.load{finished in
if finished {
appender(viewModel)
}
}
}
}
func save(){
do {
let _ = try db.collection(model.collection).addDocument(from: model)
} catch {
print(error)
}
}
func update(){
if let id = model.id {
do {
try db.collection(model.collection).document(id).setData(from: model)
} catch {
print(error.localizedDescription)
}
}
}
func delete(){
if let id = model.id {
db.collection(model.collection).document(id).delete() { error in
if let error = error {
print(error.localizedDescription)
}
}
}
}
}
I had the same problem, I am solved it after I updated Quick/Nimble.
I guess some pod project with x86 files meed to update to support M1
Well for the record, anybody who is experiencing these odd bugs on M1 must read exactly inside the excode compilation error.
If they are saying a specific class it means xcode can't compile your code and you should just remove the code and try to compile line by line.
I know that's very strange, looks like programming PHP and refreshing a webpage but I'm sure this type of bug can be related to the platform migration.
In my situation, I had a class that was OK, I started refactoring and instead of xcode showing me the compilation errors, it gave this cryptic BPT5, at reading exactly the description inside of the IDE I could find that my class was the root cause.
Just remove the code all over you changed and try to compile it again...
Sorry for the late update on that. But in my case it was either CocoaPods in general or the Firebase Pods, which were not compatible with Apple Silicon at that time.
I was just using Swift Package Manager and with that, it worked.
I do not know though, if the problem still exists, because I didn't build another app on the M1 MacBook.
I run into this issue after I accidently extract a view as variable without awareness. You shall check your recently committed code to figure it out.

OpenWeatherMap API does not find all cities

I was following this tutorial (https://www.youtube.com/watch?v=QMR6N6Vs_x0&feature=emb_err_woyt) and used the code for an Apple Watch Application. So everything works almost fine.
The strange thing is that not all the cities that I'm typing are accepted. It gives the error (see code) "incorrect user input!". But when I copy and paste the url that is generated and not correct due the code, it will work in de browser. It will accept that input. But my app won't.
I have searched but could not find anything similar. I think it is a very small thing to fix.
import Foundation
final class NetService: ObservableObject {
#Published var weather: WeatherData?
#Published var city = ""
let baseURL = URL(string: "https://api.openweathermap.org/data/2.5/weather")!
var query = ["q": "", "appid": "", "units": "metric"]
func loadWeatherInfo(by city: String) {
guard baseURL.withQueries(query) != nil, city != "" else { print("URL isn't correct!"); return}
query["appid"] = "HERE I PUT MY OWN API-KEY"
query["q"] = city
URLSession.shared.dataTask(with: baseURL.withQueries(query)!) { data, _, error in
print(self.baseURL.withQueries(self.query)!)
guard let data = data else { print(#line, #function, "\(error!.localizedDescription)"); return }
if let weatherInfo = try? JSONDecoder().decode(WeatherData.self, from: data) {
DispatchQueue.main.async { [weak self] in
self?.weather = weatherInfo
}
} else {
print(#line, #function, "incorrect user input!"); return
}
}.resume()
}
}
So my question is, why won't my code accept all the cities. I get the error on "Zeist", "Woerden", "Gent" and many more. If I copy the "build url" (https://api.openweathermap.org/data/2.5/weather?q=Zeist&appid=HERE I PUT MY OWN API-KEY&units=metric) from the console I get a return when i paste this in Safari. So the city is recognised.
{"coord":{"lon":5.23,"lat":52.09},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"base":"stations","main":{"temp":11.84,"feels_like":10.18,"temp_min":11.11,"temp_max":12.78,"pressure":1020,"humidity":58},"wind":{"speed":0.45,"deg":264,"gust":2.68},"clouds":{"all":7},"dt":1589534449,"sys":{"type":3,"id":2005313,"country":"NL","sunrise":1589514260,"sunset":1589570804},"timezone":7200,"id":2743977,"name":"Zeist","cod":200}
So the url is build correctly.
Thank for reading, I hope there is a solution.
Thanks to the comments above this post I have found the solution. In my WeatherData visibility is something that I collect from OpenWeatherMap. Not all cities have "visibility" as an output, so I have made this an optional and it works now!

How to print the current local network name in swift

I am creating an iOS app which displays the current local network name at the top of the screen, and so forth. I am trouble-shooting different ways to display this but I can't manage the current program. Can someone help me out?
I've looked at several GitHub, stack overflow, and youtube comments about this, but nome of them worked.
In the current Xcode I'm using which is Xcode(10.4.2) I'm using a label(correct me if I should use something else) to display the current Wifi named --> (WiFi: ......)
Please don't test on the simulator, use the iphone for testing.
Import SystemConfiguration :
import SystemConfiguration.CaptiveNetwork
In ViewDidLoad :
let wifiName = getWiFiName()
print("Wifi: \(String(describing: wifiName))")
Function :
func getWiFiName() -> String? {
var serviceSetIdentifier:String?
if let interfaces = CNCopySupportedInterfaces() as Array? {
interfaces.forEach { interface in
guard let interfaceInfo = CNCopyCurrentNetworkInfo(interface as! CFString) as NSDictionary? else { return }
serviceSetIdentifier = interfaceInfo[kCNNetworkInfoKeySSID as String] as? String
}
}
return serviceSetIdentifier
}

Locksmith error: Locksmith.LocksmithError.interactionNotAllowed

we're using Locksmith to save user data for Keychain. In our end everything works as it should but for some reason we receive crashes with the error Locksmith.LocksmithError.interactionNotAllowed.
Follows the code where the crash happen:
func updateUserAccessToken(forAccount account: String, token: String) {
var userAccessToken = Locksmith.loadDataForUserAccount(userAccount: account) ?? [String: Any]()
userAccessToken[“token”] = token
try! Locksmith.updateData(data: userAccessToken, forUserAccount: account)
}
Why is the code above crashes for other users? 'til . now we can't replicate the said crash. Any help is very much appreciated. Thanks!
UPDATE:
So we finally able to replicate this crash, and it's because we're accessing the keychain while the device is locked. I found out you can change the Keychain's "accessibility option" but I'm not sure how to do it in Locksmith. Anybody?
I found out that if you're using a protocol base approach changing the accessibility option is much more easier. But unfortunately our app doesn't use it. So what I did was I created an extension as follow:
extension Locksmith {
fileprivate static func loadDataForUserAccount(userAccount: String,
inService service: String = LocksmithDefaultService,
accessibleOption: LocksmithAccessibleOption) -> [String: Any]? {
struct ReadRequest: GenericPasswordSecureStorable, ReadableSecureStorable {
let service: String
let account: String
var accessible: LocksmithAccessibleOption?
}
let request = ReadRequest(service: service, account: userAccount, accessible: accessibleOption)
return request.readFromSecureStore()?.data
}
fileprivate static func updateData(data: [String: Any],
forUserAccount userAccount: String,
inService service: String = LocksmithDefaultService,
accessibleOption: LocksmithAccessibleOption) throws {
struct UpdateRequest: GenericPasswordSecureStorable, CreateableSecureStorable {
let service: String
let account: String
let data: [String: Any]
var accessible: LocksmithAccessibleOption?
}
let request = UpdateRequest(service: service, account: userAccount, data: data, accessible: accessibleOption)
return try request.updateInSecureStore()
}
}
NOTE: changing the "accessibility option" may loss your access to the data previously saved with the default "accessibility option". If you need those data you may need to handle it separately.

Struggling with Vapor Client

I'm trying to make a simple get request to Google Places API from my vapor web service.
This is what my controller looks like:
import Vapor
import HTTP
import VaporPostgreSQL
final class MainController {
var currentDroplet: Droplet!
func addRoutes(drop: Droplet) {
currentDroplet = drop
drop.get("places",String.self, String.self, handler: getNearbyPlaces)
}
func getNearbyPlaces(request: Request, lat: String, long: String) throws -> ResponseRepresentable {
let googleAPIKey = "MY_KEY"
let googlePlacesBaseURL = "https://maps.googleapis.com/maps/api/place/nearbysearch"
let url = googlePlacesBaseURL + "/json?location=\(lat),\(long)&radius=500&types=food&key=" + googleAPIKey
print(url)
let apiResponse = try drop.client.get(url)
print(apiResponse)
return apiResponse.json != nil ? apiResponse.json! : "Something went bad"
}
}
It should be as simple as that, however when I call it, the request keeps hanging for a long time and then it returns 500.
Note that the printed url in the console does work fine directly in the browser.
I couldn't figure out a useful way to catch and debug any errors too.
I needed to add import Foundation and drop.client = FoundationClient.self to main.swift to get a similar call to work.