IndeedHTMLJobParserTests.swift 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. //
  2. // IndeedHTMLJobParserTests.swift
  3. // indeed_ai_job_searchTests
  4. //
  5. import XCTest
  6. @testable import indeed_ai_job_search
  7. final class IndeedHTMLJobParserTests: XCTestCase {
  8. func testDetectsCaptchaPage() {
  9. let html = """
  10. <html><title>Security Check - Indeed.com</title>
  11. <script>window.INDEED_CLOUDFLARE_STATIC_PAGE={PAGE_TYPE:"captcha"};</script>
  12. </html>
  13. """
  14. XCTAssertTrue(IndeedHTMLJobParser.isBlockedOrCaptcha(html))
  15. XCTAssertThrowsError(
  16. try IndeedHTMLJobParser.parse(
  17. html: html,
  18. pageURL: URL(string: "https://www.indeed.com/jobs")!
  19. )
  20. ) { error in
  21. guard case IndeedDataError.forbidden = error else {
  22. return XCTFail("Expected forbidden, got \(error)")
  23. }
  24. }
  25. }
  26. func testParsesEmbeddedJobKeyJSON() throws {
  27. let html = """
  28. <html><body>
  29. <script>
  30. var results = [
  31. {
  32. "jobkey": "5185d0dd8c286093",
  33. "displayTitle": "iOS Engineer",
  34. "company": "Tuyo",
  35. "formattedLocation": "Remote",
  36. "snippet": "Build SwiftUI apps",
  37. "link": "/viewjob?jk=5185d0dd8c286093",
  38. "jobTypes": ["Full-time"],
  39. "salarySnippet": { "text": "$120,000 - $160,000 a year" }
  40. },
  41. {
  42. "jobKey": "a1b2c3d4e5f60718",
  43. "title": "Senior iOS Developer",
  44. "companyName": "Acme Labs",
  45. "formattedLocation": "Lahore, Pakistan",
  46. "snippet": "PKR roles",
  47. "url": "https://pk.indeed.com/viewjob?jk=a1b2c3d4e5f60718"
  48. }
  49. ];
  50. </script>
  51. <div data-jk="5185d0dd8c286093"></div>
  52. </body></html>
  53. """
  54. let pageURL = URL(string: "https://www.indeed.com/jobs?q=iOS")!
  55. let dtos = try IndeedHTMLJobParser.parse(html: html, pageURL: pageURL)
  56. XCTAssertGreaterThanOrEqual(dtos.count, 2)
  57. let tuyo = try XCTUnwrap(dtos.first { $0.jobKey == "5185d0dd8c286093" })
  58. XCTAssertEqual(tuyo.title, "iOS Engineer")
  59. XCTAssertEqual(tuyo.company, "Tuyo")
  60. XCTAssertTrue(tuyo.url.contains("5185d0dd8c286093"))
  61. XCTAssertTrue(IndeedBrowseURLBuilder.isLikelyRealJobKey(tuyo.jobKey ?? ""))
  62. let mapped = try IndeedListingMapper.map(tuyo)
  63. XCTAssertEqual(mapped.company, "Tuyo")
  64. XCTAssertTrue(mapped.indeedURL.absoluteString.contains("viewjob"))
  65. XCTAssertFalse(IndeedBrowseURLBuilder.openableURL(for: mapped).usedBrowseFallback)
  66. }
  67. func testParsesDataJKHTMLCards() throws {
  68. let html = """
  69. <html><body>
  70. <div class="cardOutline" data-jk="abcdef0123456789">
  71. <h2 class="jobTitle"><a href="/viewjob?jk=abcdef0123456789">SwiftUI Developer</a></h2>
  72. <span data-testid="company-name">Nexlify Studio</span>
  73. <div data-testid="text-location">Lahore, Pakistan</div>
  74. <div data-testid="attribute_snippet_testid">Rs 1,800,000 - Rs 2,800,000 a year</div>
  75. </div>
  76. </body></html>
  77. """
  78. let dtos = try IndeedHTMLJobParser.parse(
  79. html: html,
  80. pageURL: URL(string: "https://pk.indeed.com/jobs")!
  81. )
  82. let job = try XCTUnwrap(dtos.first)
  83. XCTAssertEqual(job.jobKey, "abcdef0123456789")
  84. XCTAssertEqual(job.title, "SwiftUI Developer")
  85. XCTAssertEqual(job.company, "Nexlify Studio")
  86. XCTAssertEqual(job.salaryCurrency, "PKR")
  87. }
  88. }
  89. final class IndeedSearchURLBuilderTests: XCTestCase {
  90. func testBuildsPakistanHostFromCountry() {
  91. var criteria = SearchCriteria()
  92. criteria.jobTitle = "iOS Developer"
  93. criteria.country = "Pakistan"
  94. criteria.city = "Lahore"
  95. let url = IndeedSearchURLBuilder.searchURL(criteria: criteria, query: "iOS developer")
  96. XCTAssertEqual(url.host, "pk.indeed.com")
  97. XCTAssertTrue(url.path.contains("/jobs"))
  98. let q = URLComponents(url: url, resolvingAgainstBaseURL: false)?
  99. .queryItems?.first { $0.name == "q" }?.value
  100. XCTAssertEqual(q, "iOS Developer")
  101. }
  102. func testRemoteOnlySetsLocationRemote() {
  103. var criteria = SearchCriteria()
  104. criteria.jobTitle = "iOS"
  105. criteria.remoteTypes = [.remote]
  106. let url = IndeedSearchURLBuilder.searchURL(criteria: criteria, query: "iOS")
  107. let loc = URLComponents(url: url, resolvingAgainstBaseURL: false)?
  108. .queryItems?.first { $0.name == "l" }?.value
  109. XCTAssertEqual(loc, "Remote")
  110. }
  111. }
  112. final class JobListingFilterUnknownFieldTests: XCTestCase {
  113. func testKeepsListingWhenEmploymentUnknownButFilterSet() {
  114. var listing = JobListing(
  115. indeedJobKey: "5185d0dd8c286093",
  116. title: "iOS Developer",
  117. company: "Acme",
  118. location: "Remote",
  119. remoteType: .remote,
  120. employmentType: nil,
  121. postedAt: Date(),
  122. snippet: "Swift",
  123. indeedURL: URL(string: "https://www.indeed.com/viewjob?jk=5185d0dd8c286093")!
  124. )
  125. var criteria = SearchCriteria()
  126. criteria.jobTitle = "iOS"
  127. criteria.employmentTypes = [.fullTime]
  128. let filtered = JobListingFilter.filter([listing], criteria: criteria, query: "iOS")
  129. XCTAssertEqual(filtered.count, 1)
  130. listing.employmentType = .partTime
  131. let dropped = JobListingFilter.filter([listing], criteria: criteria, query: "iOS")
  132. XCTAssertTrue(dropped.isEmpty)
  133. }
  134. }