extends GdUnitTestSuite ## Character pictures cached on disk: downloaded once, then only again when they changed ## on the server, or when they are old and the server can't tell. var _server: FakeHttpTransport var _config_dir_before: String var _png: PackedByteArray func before_test() -> void: _server = FakeHttpTransport.new().install() _config_dir_before = OS.get_environment(Settings.CONFIG_DIR_ENV) OS.set_environment(Settings.CONFIG_DIR_ENV, create_temp_dir("character_cache")) if DirAccess.dir_exists_absolute(CharacterCache.folder()): for file_name: String in DirAccess.get_files_at(CharacterCache.folder()): DirAccess.remove_absolute(CharacterCache.folder().path_join(file_name)) var image: Image = Image.create(4, 4, false, Image.FORMAT_RGB8) image.fill(Color.ORANGE) _png = image.save_png_to_buffer() func after_test() -> void: OS.set_environment(Settings.CONFIG_DIR_ENV, _config_dir_before) _server.uninstall() func _serve(characters: Dictionary) -> void: var details: Array = [] for character_name: String in characters: details.append({"name": character_name, "modified": characters[character_name]}) _server.respond_bytes(_image_path(character_name), _png) _server.respond_json("/character/details", details) # Serves the names only, like a server older than /character/details func _serve_names_only(names: Array[String]) -> void: _server.respond_json("/character/list", names) for character_name: String in names: _server.respond_bytes(_image_path(character_name), _png) func _image_path(character_name: String) -> String: return "/character?name=" + character_name.uri_encode() # Loads the list and every picture with a new cache, like starting the game func _load_all() -> Array[Image]: var cache: CharacterCache = CharacterCache.new() var images: Array[Image] = [] cache.load_list(func(names: Array[String]) -> void: for character_name: String in names: cache.fetch_image(character_name, func(image: Image) -> void: images.append(image))) return images func _index() -> Dictionary: return JSON.parse_string(FileAccess.get_file_as_string( CharacterCache.folder().path_join(CharacterCache.INDEX_FILE_NAME))) func _set_index(index: Dictionary) -> void: FileAccess.open(CharacterCache.folder().path_join(CharacterCache.INDEX_FILE_NAME), FileAccess.WRITE).store_string(JSON.stringify(index)) func test_first_load_downloads_and_keeps_every_picture() -> void: _serve({"Crono.png": 100, "Marle.png": 200}) var images: Array[Image] = _load_all() assert_int(images.size()).is_equal(2) assert_int(_server.count(_image_path("Crono.png"))).is_equal(1) assert_bool(FileAccess.file_exists(CharacterCache.folder().path_join("Crono.png"))).is_true() assert_int(int(_index()["Marle.png"]["modified"])).is_equal(200) func test_unchanged_pictures_come_from_disk() -> void: _serve({"Crono.png": 100, "Marle.png": 200}) _load_all() var images: Array[Image] = _load_all() assert_int(images.size()).is_equal(2) assert_int(_server.count(_image_path("Crono.png"))).is_equal(1) assert_int(_server.count(_image_path("Marle.png"))).is_equal(1) func test_only_changed_pictures_are_downloaded_again() -> void: _serve({"Crono.png": 100, "Marle.png": 200}) _load_all() _serve({"Crono.png": 100, "Marle.png": 300}) _load_all() assert_int(_server.count(_image_path("Crono.png"))).is_equal(1) assert_int(_server.count(_image_path("Marle.png"))).is_equal(2) assert_int(int(_index()["Marle.png"]["modified"])).is_equal(300) func test_new_character_is_downloaded() -> void: _serve({"Crono.png": 100}) _load_all() _serve({"Crono.png": 100, "Lucca.png": 100}) var images: Array[Image] = _load_all() assert_int(images.size()).is_equal(2) assert_int(_server.count(_image_path("Lucca.png"))).is_equal(1) func test_characters_removed_on_the_server_are_deleted() -> void: _serve({"Crono.png": 100, "Magus.png": 100}) _load_all() _serve({"Crono.png": 100}) _load_all() assert_bool(FileAccess.file_exists(CharacterCache.folder().path_join("Magus.png"))) \ .is_false() assert_bool(_index().has("Magus.png")).is_false() func test_broken_cached_picture_is_downloaded_again() -> void: _serve({"Crono.png": 100}) _load_all() FileAccess.open(CharacterCache.folder().path_join("Crono.png"), FileAccess.WRITE) \ .store_string("not a picture") var images: Array[Image] = _load_all() assert_int(images.size()).is_equal(1) assert_int(_server.count(_image_path("Crono.png"))).is_equal(2) func test_older_server_lists_names_and_young_pictures_come_from_disk() -> void: _serve_names_only(["Crono.png"]) _load_all() var images: Array[Image] = _load_all() assert_int(images.size()).is_equal(1) assert_int(_server.count("/character/details")).is_equal(2) assert_int(_server.count(_image_path("Crono.png"))).is_equal(1) assert_int(int(_index()["Crono.png"]["modified"])).is_equal(CharacterCache.UNKNOWN_TIME) func test_older_server_pictures_are_downloaded_again_when_old() -> void: _serve_names_only(["Crono.png", "Marle.png"]) _load_all() var index: Dictionary = _index() var too_old: int = int(Time.get_unix_time_from_system()) - CharacterCache.MAX_AGE_SECONDS - 1 index["Marle.png"]["downloaded"] = too_old _set_index(index) _load_all() assert_int(_server.count(_image_path("Crono.png"))).is_equal(1) assert_int(_server.count(_image_path("Marle.png"))).is_equal(2) func test_picture_cached_without_a_time_is_downloaded_once_the_server_has_times() -> void: _serve_names_only(["Crono.png"]) _load_all() _serve({"Crono.png": 100}) _load_all() _load_all() assert_int(_server.count(_image_path("Crono.png"))).is_equal(2) func test_picture_that_cannot_be_read_is_not_cached() -> void: _server.respond_json("/character/details", [{"name": "Crono.png", "modified": 100}]) _server.respond_bytes(_image_path("Crono.png"), "not a picture".to_utf8_buffer()) var images: Array[Image] = _load_all() assert_array(images).is_empty() assert_bool(FileAccess.file_exists(CharacterCache.folder().path_join("Crono.png"))).is_false() func test_broken_index_starts_over() -> void: _serve({"Crono.png": 100}) _load_all() FileAccess.open(CharacterCache.folder().path_join(CharacterCache.INDEX_FILE_NAME), FileAccess.WRITE).store_string("{broken") var images: Array[Image] = _load_all() assert_int(images.size()).is_equal(1) assert_int(_server.count(_image_path("Crono.png"))).is_equal(2) func test_cached_pictures_are_shown_when_the_server_cannot_be_reached() -> void: _serve({"Crono.png": 100, "Marle.png": 200}) _load_all() _server.respond_json("/character/details", {}, 503) _server.respond_json("/character/list", {}, 503) var images: Array[Image] = _load_all() assert_int(images.size()).is_equal(2) assert_int(_server.count(_image_path("Crono.png"))).is_equal(1) assert_bool(FileAccess.file_exists(CharacterCache.folder().path_join("Marle.png"))).is_true() func test_nothing_is_shown_without_a_server_or_cached_pictures() -> void: var cache: CharacterCache = CharacterCache.new() var listed: Array = [] cache.load_list(func(names: Array[String]) -> void: listed.append(names)) assert_array(listed).has_size(1) assert_array(listed[0]).is_empty()