
set testmodule [file normalize tests/modules/block_keyspace_notification.so]

start_server {tags {"modules"}} {
    r module load $testmodule
    test {Blocking keyspace notification one} {
        wait_for_blocked_clients_count 0
        r b_keyspace.clear
        assert_equal "1" [r hset a b c]
        assert_equal "{event hset key a}" [r b_keyspace.events]
    }
    test {Blocking keyspace notification two} {
        wait_for_blocked_clients_count 0
        r b_keyspace.clear
        set rd1 [valkey_deferring_client]
        $rd1 hset b c d
        after 500
        set rd2 [valkey_deferring_client]
        $rd2 hset c d e
        wait_for_blocked_clients_count 2
        assert_equal "" [r b_keyspace.events]
        wait_for_blocked_clients_count 1
        assert_equal "{event hset key b}" [r b_keyspace.events]
        wait_for_blocked_clients_count 0
        assert_equal "{event hset key b} {event hset key c}" [r b_keyspace.events]
    }
    test {Blocking keyspace notification with pipelining hset after hget} {
        wait_for_blocked_clients_count 0
        r b_keyspace.clear
        set rd1 [valkey_deferring_client]
        $rd1 hset key_10 field_10 value_10
        wait_for_blocked_clients_count 0
        assert_equal "1" [$rd1 read]
        pause_process [srv 0 pid]
        # Queue up three commands: hget (non-blocking), hset (blocking), hget (non-blocking)
        $rd1 hget key_10 field_10
        $rd1 hset key_10 value_10 ss
        $rd1 hget key_10 field_10
        resume_process [srv 0 pid]
        # First hget should return immediately (non-blocking)
        assert_equal "value_10" [$rd1 read]
        # hset triggers blocking keyspace notification - wait for it to block
        wait_for_blocked_clients_count 1
        # Now wait for hset to complete
        wait_for_blocked_clients_count 0
        assert_equal "1" [$rd1 read]
        # Second hget returns after hset completes
        assert_equal "value_10" [$rd1 read]
        $rd1 close
    }
    test {Blocking keyspace notification with pipelining hget after hset} {
        wait_for_blocked_clients_count 0
        r b_keyspace.clear
        set rd1 [valkey_deferring_client]
        pause_process [srv 0 pid]
        $rd1 hset key_2 field_1 value_1
        $rd1 hget key_2 field_1
        resume_process [srv 0 pid]
        wait_for_blocked_clients_count 1
        assert_equal "" [r b_keyspace.events]
        wait_for_blocked_clients_count 0
        assert_equal "{event hset key key_2}" [r b_keyspace.events]
        assert_equal "1" [$rd1 read]
        assert_equal "value_1" [$rd1 read]
    }
    test {Blocking keyspace notif during multi} {
        wait_for_blocked_clients_count 0
        r b_keyspace.clear
        r multi
        r hset d e f
        r hset e f g
        assert_equal "1 1" [r exec]
        # Nothing should be blocked inside the transaction
        assert_equal [s 0 blocked_clients] 0
        assert_equal [r b_keyspace.events] ""
        # In the background, the work should still occur
        wait_for_condition 1000 50 {
            [string match "{event hset key *} {event hset key *}" [set latest [r b_keyspace.events]]]
        } else {
            fail "Keyspace event not propagated within 5 seconds"
        }
    }
    test {Event that fires twice} {
        wait_for_blocked_clients_count 0
        r b_keyspace.clear
        r hset f g h
        wait_for_blocked_clients_count 0
        assert_equal "OK" [r b_keyspace.clear]
        set rd1 [valkey_deferring_client]
        $rd1 RENAME f g
        # Only one blocked client
        assert_equal [s 0 blocked_clients] 1
        assert_equal "" [r b_keyspace.events]
        # We should still see async processing for both events, which could be processed in either order
        wait_for_condition 1000 50 {
            [string match "{event rename_* key *} {event rename_* key *}" [set latest [r b_keyspace.events]]]
        } else {
            fail "Rename event not propagated within 5 seconds: latest value ${latest}"
        }
    }
    test {Server-created keyspace notification} {
        wait_for_blocked_clients_count 0
        r b_keyspace.clear
        assert_equal "1" [r hset h i j]
        assert_equal "1" [r expire h 1]
        assert_equal [s 0 blocked_clients] 0
        # Expire will proceed processing in the background as we haven't
        # enabled blocking for the command yet.
        wait_for_condition 1000 50 {
            [r b_keyspace.events] eq "{event hset key h} {event expire key h}"
        } else {
            fail "Expire event not propagated within 5 seconds"
        }
        assert_equal "OK" [r b_keyspace.clear]
        # Expiration should happen in the background
        wait_for_condition 1000 50 {
            [r b_keyspace.events] eq "{event expired key h}"
        } else {
            fail "Expired event not propagated within 5 seconds"
        }
    }

    test {HFE commands with blocking keyspace notify} {
        r del h1

        wait_for_blocked_clients_count 0
        r b_keyspace.clear
        r hset h1 f1 v1 f2 v2
        wait_for_blocked_clients_count 0

        # HEXPIRE: updated + expired(del)
        r b_keyspace.clear
        assert_equal "1" [r hexpire h1 100 FIELDS 1 f1]
        assert_equal {{event hexpire key h1}} [r b_keyspace.events]
        r b_keyspace.clear
        assert_equal "2" [r hexpire h1 0 FIELDS 1 f1]
        assert_equal {{event hexpired key h1}} [r b_keyspace.events]

        # HGETDEL
        r b_keyspace.clear
        assert_equal "v2" [r hgetdel h1 FIELDS 1 f2]
        # "hdel" and "del" notifications run on independent background threads.
        # The client is unblocked by the "hdel" thread, so by the time HGETDEL
        # returns the "del" thread may not have logged yet — wait for both.
        wait_for_condition 50 100 {
            [llength [r b_keyspace.events]] >= 2
        } else {
            fail "Did not see both hdel and del events: [r b_keyspace.events]"
        }
        assert_equal [lsort {{event hdel key h1} {event del key h1}}] [lsort [r b_keyspace.events]]
        assert_equal "0" [r exists h1]

        # HPERSIST
        r hset h1 f1 v1
        r hexpire h1 100 FIELDS 1 f1
        wait_for_blocked_clients_count 0
        r b_keyspace.clear
        assert_equal "1" [r hpersist h1 FIELDS 1 f1]
        assert_equal {{event hpersist key h1}} [r b_keyspace.events]

        r del h1
    }

    test "Unload the module - testblockingkeyspacenotif" {
        assert_equal {OK} [r module unload testblockingkeyspacenotif]
    }
}

