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
4 changes: 2 additions & 2 deletions Stream-iOS/Stream-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 6.0;
Expand Down Expand Up @@ -389,7 +389,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 6.0;
Expand Down
27 changes: 27 additions & 0 deletions Stream-iOS/Stream-iOS/Core/Models/CharacterDTO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// CharacterDTO.swift
// Stream-iOS
//
// Created by Ullash Podder on 14/07/2026.
//
import Foundation

struct CharacterDTO: Decodable, Sendable, Identifiable {
let id: Int
let name: String
let status: CharacterStatus
let species: String
let gender: String
let image: URL
}

enum CharacterStatus: String, Decodable, Sendable {
case alive = "Alive"
case dead = "Dead"
case unknown

init(from decoder: Decoder) throws {
let raw = try decoder.singleValueContainer().decode(String.self)
self = CharacterStatus(rawValue: raw) ?? .unknown
}
}
20 changes: 20 additions & 0 deletions Stream-iOS/Stream-iOS/Core/Models/PagedResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// PagedResponse.swift
// Stream-iOS
//
// Created by Ullash Podder on 14/07/2026.
//

import Foundation
struct PagedResponse<T: Decodable & Sendable>: Decodable, Sendable {
let info: PageInfo
let results: [T]
}

struct PageInfo: Decodable, Sendable {
let count: Int
let pages: Int
let next: URL?
let prev: URL?
}

44 changes: 44 additions & 0 deletions Stream-iOS/Stream-iOSTests/CharacterDecodingTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// CharacterDecodingTests.swift
// Stream-iOS
//
// Created by Ullash Podder on 14/07/2026.
//

import Testing
import Foundation
@testable import Stream_iOS

struct CharacterDecodingTests {

@Test func decodesPagedResponseFromFixture() throws {
let data = try Fixture.load("character_page1")
let response = try JSONDecoder().decode(PagedResponse<CharacterDTO>.self, from: data)

#expect(response.info.count == 826)
#expect(response.info.pages == 42)
#expect(response.results.count == 20)
#expect(response.info.next != nil)
#expect(response.info.prev == nil)
}

@Test func decodesFirstCharacterCorrectly() throws {
let data = try Fixture.load("character_page1")
let response = try JSONDecoder().decode(PagedResponse<CharacterDTO>.self, from: data)
let rick = try #require(response.results.first)

#expect(rick.id == 1)
#expect(rick.name == "Rick Sanchez")
#expect(rick.status == .alive)
#expect(rick.species == "Human")
#expect(rick.image.absoluteString.hasSuffix("/avatar/1.jpeg"))
}

@Test func decodesUnknownStatusAsFallback() throws {
let json = #"{"id":1,"name":"X","status":"Martian","species":"Y","gender":"Male","image":"https://a.com/1.jpeg"}"#
let dto = try JSONDecoder().decode(CharacterDTO.self, from: Data(json.utf8))

#expect(dto.status == .unknown)
}
}

22 changes: 22 additions & 0 deletions Stream-iOS/Stream-iOSTests/Fixtures/Fixture.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// Fixture.swift
// Stream-iOS
//
// Created by Ullash Podder on 14/07/2026.
//

import Foundation

enum Fixture {
enum Error: Swift.Error { case notFound(String) }

static func load(_ name: String) throws -> Data {
let bundle = Bundle(for: BundleToken.self)
guard let url = bundle.url(forResource: name, withExtension: "json") else {
throw Error.notFound(name)
}
return try Data(contentsOf: url)
}

private final class BundleToken {}
}
Loading