// // OpenAIIndeedWebSearchTests.swift // indeed_ai_job_searchTests // import XCTest @testable import indeed_ai_job_search final class OpenAIResponsesClientParsingTests: XCTestCase { func testExtractsConvenienceOutputText() throws { let json = """ { "output_text": "[{ \\"title\\": \\"iOS\\" }]" } """.data(using: .utf8)! let text = try OpenAIResponsesClient.extractOutputText(from: json) XCTAssertTrue(text.contains("iOS")) } func testExtractsMessageOutputParts() throws { let json = """ { "output": [ { "type": "web_search_call", "status": "completed" }, { "type": "message", "role": "assistant", "content": [ { "type": "output_text", "text": "[{\\"title\\":\\"Swift Engineer\\"}]" } ] } ] } """.data(using: .utf8)! let text = try OpenAIResponsesClient.extractOutputText(from: json) XCTAssertTrue(text.contains("Swift Engineer")) } } final class OpenAIIndeedWebSearchParsingTests: XCTestCase { func testDecodesMarkdownFencedJSON() throws { let output = """ ```json [ { "title": "iOS Developer", "company": "Tuyo", "location": "Remote", "url": "https://www.indeed.com/viewjob?jk=5185d0dd8c286093", "jobKey": "5185d0dd8c286093", "remoteType": "remote", "salaryMin": 120000, "salaryMax": 160000, "salaryCurrency": "USD", "salaryPeriod": "year", "posted_days_ago": 2, "accepting_applications": true } ] ``` """ let dtos = try OpenAIIndeedWebSearchDataSource.decodeJobDTOs(from: output) XCTAssertEqual(dtos.count, 1) let listing = try IndeedListingMapper.map(dtos[0]) XCTAssertEqual(listing.company, "Tuyo") XCTAssertEqual(listing.indeedJobKey, "5185d0dd8c286093") XCTAssertTrue(listing.indeedURL.absoluteString.contains("viewjob")) XCTAssertEqual(listing.salary?.currency, "USD") XCTAssertFalse(IndeedBrowseURLBuilder.openableURL(for: listing).usedBrowseFallback) } func testDropsRowsMissingTitleOrURL() throws { let output = """ [ { "title": "", "company": "Acme", "url": "https://www.indeed.com/viewjob?jk=aaaaaaaaaaaaaaaa", "posted_days_ago": 1, "accepting_applications": true }, { "title": "iOS", "company": "Acme", "url": "https://www.indeed.com/viewjob?jk=bbbbbbbbbbbbbbbb", "posted_days_ago": 1, "accepting_applications": true } ] """ let dtos = try OpenAIIndeedWebSearchDataSource.decodeJobDTOs(from: output) XCTAssertEqual(dtos.count, 1) XCTAssertEqual(dtos[0].title, "iOS") } func testDropsStaleAndClosedButKeepsUndatedOpen() throws { let output = """ [ { "title": "Old iOS Role", "company": "Stale Co", "url": "https://www.indeed.com/viewjob?jk=1111111111111111", "posted_days_ago": 90, "accepting_applications": true }, { "title": "Closed iOS Role", "company": "Closed Co", "url": "https://www.indeed.com/viewjob?jk=2222222222222222", "posted_days_ago": 1, "accepting_applications": false }, { "title": "Fresh iOS Role", "company": "Fresh Co", "url": "https://www.indeed.com/viewjob?jk=3333333333333333", "posted_days_ago": 3, "accepting_applications": true }, { "title": "Undated iOS Role", "company": "Mystery Co", "url": "https://www.indeed.com/viewjob?jk=4444444444444444", "accepting_applications": true } ] """ let rows = try OpenAIIndeedWebSearchDataSource.decodeJobRows(from: output) XCTAssertEqual(rows.count, 4) let kept = rows.filter(\.isAcceptingApplications).compactMap { $0.asIndeedJobDTO( maxAgeDays: OpenAIIndeedWebSearchDataSource.maxPostedDaysAgo, allowMissingDate: true ) } XCTAssertEqual(kept.map(\.company).sorted(), ["Fresh Co", "Mystery Co"]) } func testPromptRequiresRecentOpenJobs() { var criteria = SearchCriteria() criteria.jobTitle = "iOS Developer" criteria.country = "Pakistan" criteria.remoteTypes = [.remote] let prompt = OpenAIIndeedWebSearchDataSource.buildPrompt( query: "remote iOS in Pakistan", criteria: criteria, maxResults: 10, maxAgeDays: 14 ) XCTAssertTrue(prompt.lowercased().contains("indeed")) XCTAssertTrue(prompt.contains("Pakistan")) XCTAssertTrue(prompt.contains("fromage=")) XCTAssertTrue(prompt.lowercased().contains("no longer accepting") || prompt.lowercased().contains("expired")) XCTAssertTrue(prompt.contains("posted_days_ago")) } func testWebSearchModelConstantIsSet() { XCTAssertFalse(OpenAIModels.defaultWebSearchModel.isEmpty) XCTAssertNotEqual(OpenAIModels.defaultWebSearchModel, OpenAIModels.defaultChatModel) } }