| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- //
- // MockServices.swift
- // indeed_ai_job_search
- //
- import Foundation
- import Observation
- /// Plan alias — Phase 1 mock search uses `MockJobDataSource`.
- typealias MockJobSearchService = MockJobDataSource
- struct MockJobDataSource: JobDataSource {
- var delayNanoseconds: UInt64 = 700_000_000
- var shouldFail: Bool = false
- func search(criteria: SearchCriteria, query: String) async throws -> [JobListing] {
- try await Task.sleep(nanoseconds: delayNanoseconds)
- if shouldFail {
- throw MockSearchError.network
- }
- return JobListingFilter.filter(MockJobListings.all, criteria: criteria, query: query)
- }
- }
- enum MockSearchError: LocalizedError {
- case network
- var errorDescription: String? {
- switch self {
- case .network:
- return "We couldn’t reach job results. Check your connection and try again."
- }
- }
- }
- struct MockSearchInterpreter: OpenAISearchService {
- func interpret(query: String) async throws -> InterpretedSearch {
- try await Task.sleep(nanoseconds: 350_000_000)
- var criteria = SearchCriteria()
- let lower = query.lowercased()
- if lower.contains("ios") {
- criteria.jobTitle = "iOS Developer"
- } else if lower.contains("android") {
- criteria.jobTitle = "Android Developer"
- } else if lower.contains("swift") {
- criteria.jobTitle = "Swift Developer"
- } else {
- criteria.keywords = query
- }
- if lower.contains("remote") {
- criteria.remoteTypes.insert(.remote)
- }
- if lower.contains("hybrid") {
- criteria.remoteTypes.insert(.hybrid)
- }
- // Geo only when the user names a place — stay neutral otherwise.
- if lower.contains("pakistan") {
- criteria.country = "Pakistan"
- }
- if lower.contains("united states") || lower.contains("usa") || lower.contains("u.s.") {
- criteria.country = "United States"
- }
- if lower.contains("united kingdom") || lower.contains(" uk") || lower.hasSuffix(" uk") {
- criteria.country = "United Kingdom"
- }
- if lower.contains("lahore") {
- criteria.city = "Lahore"
- if criteria.country.isEmpty { criteria.country = "Pakistan" }
- }
- if lower.contains("karachi") {
- criteria.city = "Karachi"
- if criteria.country.isEmpty { criteria.country = "Pakistan" }
- }
- if lower.contains("london") {
- criteria.city = "London"
- if criteria.country.isEmpty { criteria.country = "United Kingdom" }
- }
- if lower.contains("full-time") || lower.contains("full time") {
- criteria.employmentTypes.insert(.fullTime)
- }
- if lower.contains("senior") || lower.contains("2+") || lower.contains("experience") {
- criteria.experienceLevels.insert(.senior)
- }
- return InterpretedSearch(
- naturalLanguageQuery: query,
- criteria: criteria,
- confidenceNote: "Mock AI interpretation for Phase 1 preview."
- )
- }
- }
- struct MockJobValidationService: JobValidationService {
- func validate(listing: JobListing) async -> JobAvailability {
- listing.availability
- }
- }
- @Observable
- @MainActor
- final class MockSavedJobsStore: SavedJobsStore {
- private(set) var savedJobs: [SavedJob] = []
- func isSaved(_ listing: JobListing) -> Bool {
- savedJobs.contains { $0.listing.indeedJobKey == listing.indeedJobKey }
- }
- func toggleSave(_ listing: JobListing) {
- if let index = savedJobs.firstIndex(where: { $0.listing.indeedJobKey == listing.indeedJobKey }) {
- savedJobs.remove(at: index)
- } else {
- savedJobs.insert(SavedJob(listing: listing, savedAt: .now), at: 0)
- }
- }
- func remove(_ listing: JobListing) {
- savedJobs.removeAll { $0.listing.indeedJobKey == listing.indeedJobKey }
- }
- func updateListing(_ listing: JobListing, lastVerifiedAt: Date) {
- guard let index = savedJobs.firstIndex(where: { $0.listing.indeedJobKey == listing.indeedJobKey }) else {
- return
- }
- savedJobs[index].listing = listing
- savedJobs[index].lastVerifiedAt = lastVerifiedAt
- }
- func clearAll() {
- savedJobs.removeAll()
- }
- }
- @Observable
- @MainActor
- final class MockAlertsStore: AlertsStore {
- private(set) var alerts: [JobAlert]
- private(set) var events: [AlertNotificationEvent]
- init() {
- var remoteIOS = SearchCriteria()
- remoteIOS.jobTitle = "iOS Developer"
- remoteIOS.country = "Pakistan"
- remoteIOS.remoteTypes = [.remote]
- remoteIOS.employmentTypes = [.fullTime]
- var senior = SearchCriteria()
- senior.jobTitle = "Senior iOS Developer"
- senior.country = "Pakistan"
- senior.experienceLevels = [.senior]
- self.alerts = [
- JobAlert(
- name: "Remote iOS in Pakistan",
- criteria: remoteIOS,
- frequency: .daily,
- isEnabled: true,
- lastRunAt: Date().addingTimeInterval(-3_600),
- matchCount: 12
- ),
- JobAlert(
- name: "Senior iOS roles",
- criteria: senior,
- frequency: .weekly,
- isEnabled: false,
- lastRunAt: Date().addingTimeInterval(-86_400 * 3),
- matchCount: 4
- )
- ]
- self.events = [
- AlertNotificationEvent(
- alertName: "Remote iOS in Pakistan",
- message: "12 new matching jobs since yesterday.",
- createdAt: Date().addingTimeInterval(-2_000)
- ),
- AlertNotificationEvent(
- alertName: "Senior iOS roles",
- message: "4 new senior roles this week.",
- createdAt: Date().addingTimeInterval(-86_400),
- isRead: true
- )
- ]
- }
- func upsert(_ alert: JobAlert) {
- if let index = alerts.firstIndex(where: { $0.id == alert.id }) {
- alerts[index] = alert
- } else {
- alerts.insert(alert, at: 0)
- }
- }
- func delete(_ alert: JobAlert) {
- alerts.removeAll { $0.id == alert.id }
- }
- func setEnabled(_ alert: JobAlert, enabled: Bool) {
- guard let index = alerts.firstIndex(where: { $0.id == alert.id }) else { return }
- alerts[index].isEnabled = enabled
- }
- func markEventsRead() {
- for index in events.indices {
- events[index].isRead = true
- }
- }
- }
|