Hone logo
Hone
Problems

Synchronizing a Cache with Asynchronous Data Updates in Jest

This challenge focuses on testing a system where a cache is updated asynchronously based on external data. You'll be implementing and testing a Jest-based solution to ensure the cache accurately reflects the latest data, even when updates are delayed or occur concurrently. This is a common scenario in real-world applications dealing with APIs, event streams, or background processes.

Problem Description

You are tasked with creating a Jest test suite for a simple cache synchronization mechanism. The cache stores data keyed by a string. The CacheSynchronizer class is responsible for updating the cache based on asynchronous data updates. The updates are simulated using setTimeout to represent the asynchronous nature of the data source.

The CacheSynchronizer class has the following methods:

  • get(key: string): string | undefined: Retrieves the value associated with the given key from the cache. Returns undefined if the key is not found.
  • update(key: string, value: string, delay: number): Promise<void>: Simulates an asynchronous update to the cache. It sets the value for the given key after a specified delay (in milliseconds). The update should be reflected in subsequent calls to get().

Your goal is to write Jest tests that verify the following:

  1. Initial State: The cache is initially empty. get() should return undefined for any key.
  2. Asynchronous Update: After calling update(), get() should return the correct value after the specified delay.
  3. Concurrent Updates: If multiple update() calls are made with different delays, the cache should eventually contain the latest value, even if updates arrive out of order.
  4. No Race Conditions: The cache updates should be atomic. Multiple calls to get() during the delay of an update should consistently return the same value (either the old value or the new value, but not a mix).

Examples

Example 1:

Input:
  - Initial cache: empty
  - update("key1", "value1", 100)
  - get("key1") after 50ms
  - get("key1") after 150ms
Output:
  - get("key1") after 50ms: undefined
  - get("key1") after 150ms: "value1"
Explanation: The update takes 100ms.  The first `get()` is called before the update completes, so it returns `undefined`. The second `get()` is called after the update completes, so it returns "value1".

Example 2:

Input:
  - Initial cache: empty
  - update("key1", "value1", 200)
  - update("key1", "value2", 100)
  - get("key1") after 150ms
  - get("key1") after 300ms
Output:
  - get("key1") after 150ms: "value1"
  - get("key1") after 300ms: "value2"
Explanation: The second update ("value2") completes before the first ("value1"). Therefore, the cache should contain "value2" after 300ms.

Example 3: (Concurrent Updates and Race Condition Check)

Input:
  - Initial cache: empty
  - update("key1", "value1", 100)
  - update("key1", "value2", 200)
  - get("key1") called repeatedly between 50ms and 250ms
Output:
  - All calls to get("key1") between 50ms and 250ms should return either "value1" or "value2", but not a mix.
Explanation: This tests for atomicity.  While the updates are happening concurrently, `get()` should consistently return one value or the other, not a partially updated value.

Constraints

  • delay values in update() will be non-negative integers.
  • Keys and values will be strings.
  • The test suite should be robust and cover all the scenarios described in the problem description.
  • The tests should avoid using setInterval or other polling mechanisms. Use setTimeout and Promises appropriately.
  • The CacheSynchronizer class will be provided (see below). You only need to implement the Jest tests.

Notes

  • Consider using async/await to simplify asynchronous testing.
  • Think carefully about the order in which you schedule the update() calls and when you call get().
  • The CacheSynchronizer class is provided to ensure a consistent testing environment. Do not modify the class itself.
class CacheSynchronizer {
  private cache: { [key: string]: string } = {};

  get(key: string): string | undefined {
    return this.cache[key];
  }

  async update(key: string, value: string, delay: number): Promise<void> {
    return new Promise((resolve) => {
      setTimeout(() => {
        this.cache[key] = value;
        resolve();
      }, delay);
    });
  }
}
Loading editor...
typescript