@@ -2,8 +2,10 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
22
33const mockRedis = vi . hoisted ( ( ) => ( {
44 hget : vi . fn ( ) ,
5- hset : vi . fn ( ) ,
65 hincrby : vi . fn ( ) ,
6+ sismember : vi . fn ( ) ,
7+ sadd : vi . fn ( ) ,
8+ expire : vi . fn ( ) ,
79 pipeline : vi . fn ( ) ,
810} ) ) ;
911
@@ -32,54 +34,70 @@ describe("updateClaps", () => {
3234 beforeEach ( ( ) => vi . clearAllMocks ( ) ) ;
3335
3436 it ( "increments claps for new IP" , async ( ) => {
35- mockRedis . hget . mockResolvedValue ( null ) ;
37+ mockRedis . sismember . mockResolvedValue ( 0 ) ;
3638 mockRedis . hincrby . mockResolvedValue ( 5 ) ;
37- mockRedis . hset . mockResolvedValue ( "OK" ) ;
39+ mockRedis . sadd . mockResolvedValue ( 1 ) ;
40+ mockRedis . expire . mockResolvedValue ( 1 ) ;
3841
3942 expect ( await updateClaps ( "foo.com" , 5 , "1.2.3.4" ) ) . toBe ( 5 ) ;
4043 expect ( mockRedis . hincrby ) . toHaveBeenCalledWith ( "applause:foo.com" , "claps" , 5 ) ;
41- expect ( mockRedis . hset ) . toHaveBeenCalledWith ( "applause:foo.com" , "lastIp" , "1.2.3.4" ) ;
44+ expect ( mockRedis . sadd ) . toHaveBeenCalledWith ( "applause:ips:foo.com" , expect . any ( String ) ) ;
45+ expect ( mockRedis . expire ) . toHaveBeenCalledWith ( "applause:ips:foo.com" , 86400 ) ;
46+ } ) ;
47+
48+ it ( "stores hashed IP, not raw IP" , async ( ) => {
49+ mockRedis . sismember . mockResolvedValue ( 0 ) ;
50+ mockRedis . hincrby . mockResolvedValue ( 5 ) ;
51+ mockRedis . sadd . mockResolvedValue ( 1 ) ;
52+ mockRedis . expire . mockResolvedValue ( 1 ) ;
53+
54+ await updateClaps ( "foo.com" , 5 , "1.2.3.4" ) ;
55+ const storedHash = mockRedis . sadd . mock . calls [ 0 ] [ 1 ] ;
56+ expect ( storedHash ) . not . toBe ( "1.2.3.4" ) ;
57+ expect ( storedHash ) . toMatch ( / ^ [ a - f 0 - 9 ] { 64 } $ / ) ;
4258 } ) ;
4359
4460 it ( "blocks duplicate claps from same IP" , async ( ) => {
45- mockRedis . hget
46- . mockResolvedValueOnce ( "1.2.3.4" ) // lastIp
47- . mockResolvedValueOnce ( "10" ) ; // current claps
61+ mockRedis . sismember . mockResolvedValue ( 1 ) ;
62+ mockRedis . hget . mockResolvedValue ( "10" ) ;
4863
4964 expect ( await updateClaps ( "foo.com" , 5 , "1.2.3.4" ) ) . toBe ( 10 ) ;
5065 expect ( mockRedis . hincrby ) . not . toHaveBeenCalled ( ) ;
66+ expect ( mockRedis . sadd ) . not . toHaveBeenCalled ( ) ;
5167 } ) ;
5268
5369 it ( "returns 0 when same IP and no claps stored" , async ( ) => {
54- mockRedis . hget
55- . mockResolvedValueOnce ( "1.2.3.4" ) // lastIp
56- . mockResolvedValueOnce ( null ) ; // no claps
70+ mockRedis . sismember . mockResolvedValue ( 1 ) ;
71+ mockRedis . hget . mockResolvedValue ( null ) ;
5772
5873 expect ( await updateClaps ( "foo.com" , 5 , "1.2.3.4" ) ) . toBe ( 0 ) ;
5974 } ) ;
6075
6176 it ( "allows claps from different IP" , async ( ) => {
62- mockRedis . hget . mockResolvedValueOnce ( "1.2.3.4" ) ;
77+ mockRedis . sismember . mockResolvedValue ( 0 ) ;
6378 mockRedis . hincrby . mockResolvedValue ( 15 ) ;
64- mockRedis . hset . mockResolvedValue ( "OK" ) ;
79+ mockRedis . sadd . mockResolvedValue ( 1 ) ;
80+ mockRedis . expire . mockResolvedValue ( 1 ) ;
6581
6682 expect ( await updateClaps ( "foo.com" , 5 , "5.6.7.8" ) ) . toBe ( 15 ) ;
6783 expect ( mockRedis . hincrby ) . toHaveBeenCalled ( ) ;
6884 } ) ;
6985
7086 it ( "clamps clap increment to max 10" , async ( ) => {
71- mockRedis . hget . mockResolvedValue ( null ) ;
87+ mockRedis . sismember . mockResolvedValue ( 0 ) ;
7288 mockRedis . hincrby . mockResolvedValue ( 10 ) ;
73- mockRedis . hset . mockResolvedValue ( "OK" ) ;
89+ mockRedis . sadd . mockResolvedValue ( 1 ) ;
90+ mockRedis . expire . mockResolvedValue ( 1 ) ;
7491
7592 await updateClaps ( "foo.com" , 100 , "1.2.3.4" ) ;
7693 expect ( mockRedis . hincrby ) . toHaveBeenCalledWith ( "applause:foo.com" , "claps" , 10 ) ;
7794 } ) ;
7895
7996 it ( "clamps negative values to 1" , async ( ) => {
80- mockRedis . hget . mockResolvedValue ( null ) ;
97+ mockRedis . sismember . mockResolvedValue ( 0 ) ;
8198 mockRedis . hincrby . mockResolvedValue ( 1 ) ;
82- mockRedis . hset . mockResolvedValue ( "OK" ) ;
99+ mockRedis . sadd . mockResolvedValue ( 1 ) ;
100+ mockRedis . expire . mockResolvedValue ( 1 ) ;
83101
84102 await updateClaps ( "foo.com" , - 5 , "1.2.3.4" ) ;
85103 expect ( mockRedis . hincrby ) . toHaveBeenCalledWith ( "applause:foo.com" , "claps" , 1 ) ;
0 commit comments