1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
@testable import Grain
import Nuke
import XCTest
final class StoryImageCacheTests: GrainTestCase {
// MARK: - Helpers
private func makeStory(fullsize: String) -> GrainStory {
GrainStory(
uri: "at://did:plc:test/social.grain.story/\(UUID().uuidString)",
cid: "bafy-test",
creator: GrainProfile(cid: "cid", did: "did:plc:test", handle: "test.bsky.social"),
thumb: "https://cdn.example.com/thumb.jpg",
fullsize: fullsize,
aspectRatio: AspectRatio(width: 4, height: 3),
createdAt: "2024-01-01T00:00:00Z"
)
}
/// Builds an `ImagePipeline` backed entirely by an in-memory cache so tests
/// never touch the network or the disk, and never pollute `ImagePipeline.shared`.
private func makeIsolatedPipeline() -> ImagePipeline {
var config = ImagePipeline.Configuration()
config.dataLoader = DataLoader(configuration: {
let cfg = URLSessionConfiguration.ephemeral
cfg.protocolClasses = []
return cfg
}())
config.imageCache = ImageCache()
config.dataCache = nil
return ImagePipeline(configuration: config)
}
private func seedCache(pipeline: ImagePipeline, url: URL) {
let image = UIImage(systemName: "photo") ?? UIImage()
let container = ImageContainer(image: image)
pipeline.cache.storeCachedImage(container, for: ImageRequest(url: url))
}
// MARK: - nil story
func testNilStory_returnsFalse() {
let pipeline = makeIsolatedPipeline()
XCTAssertFalse(storyFullsizeCached(nil, in: pipeline))
}
// MARK: - invalid / empty fullsize URL
func testEmptyFullsizeURL_returnsFalse() {
let pipeline = makeIsolatedPipeline()
let story = makeStory(fullsize: "")
XCTAssertFalse(storyFullsizeCached(story, in: pipeline))
}
func testInvalidFullsizeURL_returnsFalse() {
let pipeline = makeIsolatedPipeline()
let story = makeStory(fullsize: "not a url !!!")
XCTAssertFalse(storyFullsizeCached(story, in: pipeline))
}
// MARK: - valid URL, not cached
func testValidURL_notInCache_returnsFalse() {
let pipeline = makeIsolatedPipeline()
let story = makeStory(fullsize: "https://cdn.example.com/stories/abc.jpg")
XCTAssertFalse(storyFullsizeCached(story, in: pipeline))
}
// MARK: - valid URL, cached
func testValidURL_inCache_returnsTrue() throws {
let pipeline = makeIsolatedPipeline()
let urlString = "https://cdn.example.com/stories/xyz.jpg"
let story = makeStory(fullsize: urlString)
try seedCache(pipeline: pipeline, url: XCTUnwrap(URL(string: urlString)))
XCTAssertTrue(storyFullsizeCached(story, in: pipeline))
}
func testValidURL_cachedInDifferentPipeline_doesNotAffectIsolatedPipeline() throws {
let pipeline1 = makeIsolatedPipeline()
let pipeline2 = makeIsolatedPipeline()
let urlString = "https://cdn.example.com/stories/shared.jpg"
let story = makeStory(fullsize: urlString)
try seedCache(pipeline: pipeline1, url: XCTUnwrap(URL(string: urlString)))
// pipeline2 should not see pipeline1's cache
XCTAssertFalse(storyFullsizeCached(story, in: pipeline2))
}
}