| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- //
- // IndeedListingMapperTests.swift
- // indeed_ai_job_searchTests
- //
- import XCTest
- @testable import indeed_ai_job_search
- private let searchPage0JSON = """
- {
- "pageIndex": 0,
- "pageSize": 25,
- "totalEstimated": 3,
- "nextCursor": "page-1",
- "hasMore": true,
- "results": [
- {
- "jobKey": "fixture-ios-lahore-001",
- "title": "iOS Developer",
- "company": "Nexlify Studio",
- "location": "Lahore, Pakistan",
- "remoteType": "hybrid",
- "employmentType": "full-time",
- "experienceLevel": "mid",
- "salaryMin": 1800000,
- "salaryMax": 2800000,
- "salaryCurrency": "PKR",
- "salaryPeriod": "year",
- "postedAt": "2026-10-08T09:30:00Z",
- "snippet": "Build SwiftUI features for a consumer productivity app.",
- "url": "https://pk.indeed.com/jobs?q=iOS+Developer&l=Lahore%2C+Pakistan",
- "availability": "open",
- "isEasyApply": true
- },
- {
- "jobKey": "fixture-swift-remote-002",
- "title": "Senior Swift Engineer",
- "company": "Alpine Cloud",
- "location": "Remote, Pakistan",
- "remoteType": "remote",
- "employmentType": "full-time",
- "experienceLevel": "senior",
- "salaryMin": 90000,
- "salaryMax": 130000,
- "salaryCurrency": "USD",
- "salaryPeriod": "year",
- "postedAt": "2026-10-10T14:00:00Z",
- "snippet": "Own macOS and iOS modules.",
- "url": "https://www.indeed.com/jobs?q=Senior+Swift+Engineer&l=Remote",
- "availability": "open"
- },
- {
- "title": "Android Developer",
- "company": "Karachi Labs",
- "location": "Karachi, Pakistan",
- "remoteType": "onsite",
- "url": "https://pk.indeed.com/jobs?q=Android+Developer&l=Karachi%2C+Pakistan",
- "availability": "unknown",
- "snippet": "Kotlin multiplatform experiments."
- }
- ]
- }
- """
- final class IndeedListingMapperTests: XCTestCase {
- func testMapsFixturePageToJobListings() throws {
- let page = try IndeedListingMapper.decodeFixture(from: Data(searchPage0JSON.utf8))
- XCTAssertEqual(page.listings.count, 3)
- XCTAssertEqual(page.pageIndex, 0)
- XCTAssertTrue(page.hasMore)
- XCTAssertEqual(page.nextCursor, "page-1")
- let first = try XCTUnwrap(page.listings.first)
- XCTAssertEqual(first.indeedJobKey, "fixture-ios-lahore-001")
- XCTAssertEqual(first.title, "iOS Developer")
- XCTAssertEqual(first.remoteType, .hybrid)
- XCTAssertEqual(first.employmentType, .fullTime)
- XCTAssertEqual(first.experienceLevel, .mid)
- XCTAssertEqual(first.salary?.currency, "PKR")
- XCTAssertEqual(first.availability, .open)
- XCTAssertTrue(IndeedURLCanonicalizer.isIndeedHost(first.indeedURL.host ?? ""))
- }
- func testMapsBrowseURLWhenProviderKeyMissing() throws {
- let page = try IndeedListingMapper.decodeFixture(from: Data(searchPage0JSON.utf8))
- let android = try XCTUnwrap(page.listings.first { $0.title.contains("Android") })
- // No provider key + browse URL → stable hash key, openable /jobs URL.
- XCTAssertTrue(android.indeedJobKey.hasPrefix("url-"))
- XCTAssertTrue(android.indeedURL.path.contains("/jobs"))
- XCTAssertTrue(IndeedURLCanonicalizer.isIndeedHost(android.indeedURL.host ?? ""))
- }
- func testRejectsNonIndeedURL() {
- let dto = IndeedJobDTO(
- jobKey: "x",
- title: "Role",
- company: "Co",
- location: "Remote",
- url: "https://example.com/jobs/1"
- )
- XCTAssertThrowsError(try IndeedListingMapper.map(dto)) { error in
- guard case IndeedDataError.nonIndeedURL = error else {
- return XCTFail("Expected nonIndeedURL, got \(error)")
- }
- }
- }
- }
- final class IndeedURLCanonicalizerTests: XCTestCase {
- func testAcceptsRegionalHosts() throws {
- let url = try IndeedURLCanonicalizer.canonicalize("https://pk.indeed.com/viewjob?jk=abc")
- XCTAssertEqual(url.host, "pk.indeed.com")
- XCTAssertEqual(IndeedURLCanonicalizer.extractJobKey(from: url), "abc")
- }
- func testAcceptsIndeedCoUK() throws {
- let url = try IndeedURLCanonicalizer.canonicalize("http://indeed.co.uk/viewjob?jk=uk1")
- XCTAssertEqual(url.scheme, "https")
- XCTAssertEqual(url.host, "indeed.co.uk")
- }
- func testRejectsForeignJobBoard() {
- XCTAssertThrowsError(try IndeedURLCanonicalizer.canonicalize("https://linkedin.com/jobs/1"))
- }
- }
- final class FixtureIndeedJobDataSourceTests: XCTestCase {
- func testSearchPageContractFiltersByQuery() async throws {
- let page = try IndeedListingMapper.decodeFixture(from: Data(searchPage0JSON.utf8))
- let source = InlineFixtureIndeedJobDataSource(page: page)
- let results = try await source.search(criteria: SearchCriteria(), query: "iOS")
- XCTAssertTrue(results.contains { $0.title.localizedCaseInsensitiveContains("iOS") })
- }
- }
- /// Test double that satisfies `IndeedJobDataSource` without depending on app bundle copy phases.
- private struct InlineFixtureIndeedJobDataSource: IndeedJobDataSource {
- let page: IndeedSearchPage
- func searchPage(
- criteria: SearchCriteria,
- query: String,
- page: IndeedSearchPageRequest
- ) async throws -> IndeedSearchPage {
- var filtered = self.page
- let needle = query.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
- if !needle.isEmpty {
- filtered.listings = filtered.listings.filter {
- "\($0.title) \($0.company) \($0.snippet)".lowercased().contains(needle)
- }
- }
- return filtered
- }
- }
|