Skip to content
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
1 change: 1 addition & 0 deletions .serena/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/cache
67 changes: 67 additions & 0 deletions .serena/project.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby)
# * For C, use cpp
# * For JavaScript, use typescript
# Special requirements:
# * csharp: Requires the presence of a .sln file in the project folder.
language: swift

# whether to use the project's gitignore file to ignore files
# Added on 2025-04-07
ignore_all_files_in_gitignore: true
# list of additional paths to ignore
# same syntax as gitignore, so you can use * and **
# Was previously called `ignored_dirs`, please update your config if you are using that.
# Added (renamed) on 2025-04-07
ignored_paths: []

# whether the project is in read-only mode
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
# Added on 2025-04-18
read_only: false

# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
# Below is the complete list of tools for convenience.
# To make sure you have the latest list of tools, and to view their descriptions,
# execute `uv run scripts/print_tool_overview.py`.
#
# * `activate_project`: Activates a project by name.
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
# * `create_text_file`: Creates/overwrites a file in the project directory.
# * `delete_lines`: Deletes a range of lines within a file.
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
# * `execute_shell_command`: Executes a shell command.
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
# * `initial_instructions`: Gets the initial instructions for the current project.
# Should only be used in settings where the system prompt cannot be set,
# e.g. in clients you have no control over, like Claude Desktop.
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
# * `insert_at_line`: Inserts content at a given line in a file.
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
# * `list_memories`: Lists memories in Serena's project-specific memory store.
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
# * `read_file`: Reads a file within the project directory.
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
# * `remove_project`: Removes a project from the Serena configuration.
# * `replace_lines`: Replaces a range of lines within a file with new content.
# * `replace_symbol_body`: Replaces the full definition of a symbol.
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
# * `search_for_pattern`: Performs a search for a pattern in the project.
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
# * `switch_modes`: Activates modes by providing a list of their names
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
excluded_tools: []

# initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand).
initial_prompt: ""

project_name: "supabase-swift"
96 changes: 96 additions & 0 deletions Tests/AuthTests/AuthErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,100 @@ final class AuthErrorTests: XCTestCase {
XCTAssertEqual(implicitGrantRedirect.errorCode, .unknown)
XCTAssertEqual(implicitGrantRedirect.message, "Implicit grant failure")
}

func testWeakPasswordWithReasons() {
let reasons = ["length", "characters", "pwned"]
let weakPassword = AuthError.weakPassword(message: "Password is weak", reasons: reasons)

XCTAssertEqual(weakPassword.message, "Password is weak")
XCTAssertEqual(weakPassword.errorCode, .weakPassword)
XCTAssertEqual(weakPassword.errorDescription, "Password is weak")
}

func testJWTVerificationFailed() {
let jwtError = AuthError.jwtVerificationFailed(message: "Invalid JWT signature")

XCTAssertEqual(jwtError.message, "Invalid JWT signature")
XCTAssertEqual(jwtError.errorCode, .invalidJWT)
XCTAssertEqual(jwtError.errorDescription, "Invalid JWT signature")
}

func testPKCEGrantCodeExchangeWithErrorAndCode() {
let pkceError = AuthError.pkceGrantCodeExchange(
message: "Exchange failed",
error: "invalid_grant",
code: "auth_code_123"
)

XCTAssertEqual(pkceError.message, "Exchange failed")
XCTAssertEqual(pkceError.errorCode, .unknown)
}

func testAPIErrorWithDifferentCodes() {
let errorCodes: [ErrorCode] = [
.badJWT,
.sessionExpired,
.userNotFound,
.invalidCredentials,
.emailExists,
.overRequestRateLimit
]

for code in errorCodes {
let error = AuthError.api(
message: "Test error",
errorCode: code,
underlyingData: Data(),
underlyingResponse: HTTPURLResponse(
url: URL(string: "http://localhost")!,
statusCode: 400,
httpVersion: nil,
headerFields: nil
)!
)

XCTAssertEqual(error.errorCode, code)
XCTAssertEqual(error.message, "Test error")
}
}

func testErrorCodeEquality() {
XCTAssertEqual(ErrorCode.badJWT, ErrorCode("bad_jwt"))
XCTAssertEqual(ErrorCode.sessionExpired, ErrorCode("session_expired"))
XCTAssertNotEqual(ErrorCode.badJWT, ErrorCode.sessionExpired)
}

func testErrorCodeRawValue() {
XCTAssertEqual(ErrorCode.badJWT.rawValue, "bad_jwt")
XCTAssertEqual(ErrorCode.sessionExpired.rawValue, "session_expired")
XCTAssertEqual(ErrorCode.unknown.rawValue, "unknown")
}

func testErrorCodeInitWithString() {
let code1 = ErrorCode("custom_error")
XCTAssertEqual(code1.rawValue, "custom_error")

let code2 = ErrorCode(rawValue: "another_error")
XCTAssertEqual(code2.rawValue, "another_error")
}

func testErrorCodeHashable() {
let set: Set<ErrorCode> = [.badJWT, .sessionExpired, .userNotFound]
XCTAssertTrue(set.contains(.badJWT))
XCTAssertTrue(set.contains(.sessionExpired))
XCTAssertFalse(set.contains(.emailExists))
}

func testAuthErrorPatternMatching() {
let error1: Error = AuthError.sessionMissing
XCTAssertTrue(AuthError.sessionMissing ~= error1)

let error2: Error = AuthError.weakPassword(message: "weak", reasons: [])
XCTAssertTrue(AuthError.weakPassword(message: "weak", reasons: []) ~= error2)

// Test non-AuthError
struct OtherError: Error {}
let error3: Error = OtherError()
XCTAssertFalse(AuthError.sessionMissing ~= error3)
}
}
175 changes: 175 additions & 0 deletions Tests/AuthTests/JWTCryptoTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
//
// JWTCryptoTests.swift
// Supabase
//
// Created by Coverage Tests
//

import XCTest
@testable import Auth
@testable import Helpers

#if canImport(Security)
final class JWTCryptoTests: XCTestCase {

// MARK: - JWK+RSA Tests

func testRSAPublishKeyGeneration() {
// Test data from a real RS256 JWT (modulus and exponent)
// This is a sample RSA256 public key
let jwk = JWK(
kty: "RSA",
keyOps: ["verify"],
alg: "RS256",
kid: "test-key-1",
n: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
e: "AQAB",
crv: nil,
x: nil,
y: nil,
k: nil
)

// Test valid RSA key generation
let rsaKey = jwk.rsaPublishKey
XCTAssertNotNil(rsaKey, "RSA public key should be generated successfully")
}

func testRSAPublishKeyInvalidAlgorithm() {
// Test with invalid algorithm
let jwk = JWK(
kty: "RSA",
keyOps: nil,
alg: "ES256", // Wrong algorithm - should be RS256
kid: "test-key-2",
n: "test-modulus",
e: "AQAB",
crv: nil,
x: nil,
y: nil,
k: nil
)

let rsaKey = jwk.rsaPublishKey
XCTAssertNil(rsaKey, "RSA public key should be nil with wrong algorithm")
}

func testRSAPublishKeyInvalidKeyType() {
// Test with invalid key type
let jwk = JWK(
kty: "EC", // Wrong type - should be RSA
keyOps: nil,
alg: "RS256",
kid: "test-key-3",
n: "test-modulus",
e: "AQAB",
crv: nil,
x: nil,
y: nil,
k: nil
)

let rsaKey = jwk.rsaPublishKey
XCTAssertNil(rsaKey, "RSA public key should be nil with wrong key type")
}

func testRSAPublishKeyMissingModulus() {
// Test with missing modulus
let jwk = JWK(
kty: "RSA",
keyOps: nil,
alg: "RS256",
kid: "test-key-4",
n: nil, // Missing modulus
e: "AQAB",
crv: nil,
x: nil,
y: nil,
k: nil
)

let rsaKey = jwk.rsaPublishKey
XCTAssertNil(rsaKey, "RSA public key should be nil with missing modulus")
}

func testRSAPublishKeyMissingExponent() {
// Test with missing exponent
let jwk = JWK(
kty: "RSA",
keyOps: nil,
alg: "RS256",
kid: "test-key-5",
n: "test-modulus",
e: nil, // Missing exponent
crv: nil,
x: nil,
y: nil,
k: nil
)

let rsaKey = jwk.rsaPublishKey
XCTAssertNil(rsaKey, "RSA public key should be nil with missing exponent")
}

func testRSAPublishKeyInvalidBase64() {
// Test with invalid Base64URL data
let jwk = JWK(
kty: "RSA",
keyOps: nil,
alg: "RS256",
kid: "test-key-6",
n: "!!!invalid-base64!!!",
e: "AQAB",
crv: nil,
x: nil,
y: nil,
k: nil
)

let rsaKey = jwk.rsaPublishKey
XCTAssertNil(rsaKey, "RSA public key should be nil with invalid base64 modulus")
}

// MARK: - JWTAlgorithm Tests

func testRS256VerificationWithValidSignature() {
// Create a sample JWT token (this would normally come from a real auth server)
// For testing, we'll use a known-good JWT
let header = #"{"alg":"RS256","typ":"JWT"}"#
let payload = #"{"sub":"1234567890","name":"Test User","iat":1516239022}"#

guard
let headerData = header.data(using: .utf8),
let payloadData = payload.data(using: .utf8)
else {
XCTFail("Failed to create test data")
return
}

let headerB64 = Base64URL.encode(headerData)
let payloadB64 = Base64URL.encode(payloadData)

// Create a mock signature (in real scenario, this would be a proper RSA signature)
let mockSignature = Data([0x00, 0x01, 0x02, 0x03])
let signatureB64 = Base64URL.encode(mockSignature)

let jwtString = "\(headerB64).\(payloadB64).\(signatureB64)"

// Decode the JWT
guard let decoded = JWT.decode(jwtString) else {
XCTFail("Failed to decode JWT")
return
}

XCTAssertEqual(decoded.raw.header, headerB64)
XCTAssertEqual(decoded.raw.payload, payloadB64)
XCTAssertEqual(decoded.signature, mockSignature)
}

func testRS256AlgorithmType() {
let algorithm = JWTAlgorithm.rs256
XCTAssertEqual(algorithm.rawValue, "RS256")
}

}
#endif
Loading
Loading