github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/avatars/fullcaching_test.go (about)

     1  package avatars
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/http"
     7  	"os"
     8  	"runtime"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/keybase/client/go/kbhttp"
    14  	"github.com/keybase/client/go/libkb"
    15  	"github.com/keybase/client/go/protocol/keybase1"
    16  	"github.com/keybase/clockwork"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  func TestAvatarsFullCaching(t *testing.T) {
    21  	tc := libkb.SetupTest(t, "TestAvatarsFullCaching", 1)
    22  	defer tc.Cleanup()
    23  	clock := clockwork.NewFakeClock()
    24  	tc.G.SetClock(clock)
    25  
    26  	testSrv := kbhttp.NewSrv(tc.G.GetLog(), kbhttp.NewRandomPortRangeListenerSource(7000, 8000))
    27  	require.NoError(t, testSrv.Start())
    28  	testSrv.HandleFunc("/p", func(w http.ResponseWriter, req *http.Request) {
    29  		fmt.Fprintf(w, "hi")
    30  	})
    31  	testSrv.HandleFunc("/p2", func(w http.ResponseWriter, req *http.Request) {
    32  		fmt.Fprintf(w, "hi2")
    33  	})
    34  	testSrv.HandleFunc("/p3", func(w http.ResponseWriter, req *http.Request) {
    35  		fmt.Fprintf(w, "hi3")
    36  	})
    37  
    38  	cb := make(chan struct{}, 5)
    39  	a, _ := testSrv.Addr()
    40  	testSrvAddr := fmt.Sprintf("http://%s/p", a)
    41  	tc.G.API = newAvatarMockAPI(makeHandler(testSrvAddr, cb))
    42  	m := libkb.NewMetaContextForTest(tc)
    43  	source := NewFullCachingSource(tc.G, time.Hour, 1)
    44  	source.populateSuccessCh = make(chan struct{}, 5)
    45  	source.tempDir = os.TempDir()
    46  	source.StartBackgroundTasks(m)
    47  	defer source.StopBackgroundTasks(m)
    48  
    49  	t.Logf("first blood")
    50  	res, err := source.LoadUsers(m, []string{"mike"}, []keybase1.AvatarFormat{"square"})
    51  	require.NoError(t, err)
    52  	require.Equal(t, testSrvAddr, res.Picmap["mike"]["square"].String())
    53  	select {
    54  	case <-cb:
    55  	case <-time.After(20 * time.Second):
    56  		require.Fail(t, "no API call")
    57  	}
    58  	select {
    59  	case <-source.populateSuccessCh:
    60  	case <-time.After(20 * time.Second):
    61  		require.Fail(t, "no populate")
    62  	}
    63  
    64  	t.Log("cache hit")
    65  
    66  	convertPath := func(path string) string {
    67  		path = strings.TrimPrefix(path, "file://")
    68  		if runtime.GOOS == "windows" {
    69  			path = strings.ReplaceAll(path, `/`, `\`)
    70  			path = path[1:]
    71  		}
    72  		return path
    73  	}
    74  
    75  	getFile := func(path string) string {
    76  		path = convertPath(path)
    77  		file, err := os.Open(path)
    78  		require.NoError(t, err)
    79  		defer file.Close()
    80  		dat, err := io.ReadAll(file)
    81  		require.NoError(t, err)
    82  		return string(dat)
    83  	}
    84  	res, err = source.LoadUsers(m, []string{"mike"}, []keybase1.AvatarFormat{"square"})
    85  	require.NoError(t, err)
    86  	select {
    87  	case <-cb:
    88  		require.Fail(t, "no API call")
    89  	default:
    90  	}
    91  	select {
    92  	case <-source.populateSuccessCh:
    93  		require.Fail(t, "no populate")
    94  	default:
    95  	}
    96  	mikePath := res.Picmap["mike"]["square"].String()
    97  	require.NotEqual(t, testSrvAddr, mikePath)
    98  	require.True(t, strings.HasPrefix(mikePath, "file://"))
    99  	require.Equal(t, "hi", getFile(mikePath))
   100  
   101  	t.Log("stale")
   102  	testSrvAddr = fmt.Sprintf("http://%s/p2", a)
   103  	tc.G.API = newAvatarMockAPI(makeHandler(testSrvAddr, cb))
   104  	clock.Advance(2 * time.Hour)
   105  	res, err = source.LoadUsers(m, []string{"mike"}, []keybase1.AvatarFormat{"square"})
   106  	require.NoError(t, err)
   107  	select {
   108  	case <-cb:
   109  	case <-time.After(20 * time.Second):
   110  		require.Fail(t, "no API call")
   111  	}
   112  	select {
   113  	case <-source.populateSuccessCh:
   114  	case <-time.After(20 * time.Second):
   115  		require.Fail(t, "no populate")
   116  	}
   117  	mikePath2 := res.Picmap["mike"]["square"].String()
   118  	require.Equal(t, mikePath, mikePath2)
   119  	res, err = source.LoadUsers(m, []string{"mike"}, []keybase1.AvatarFormat{"square"})
   120  	require.NoError(t, err)
   121  	select {
   122  	case <-cb:
   123  		require.Fail(t, "no API call")
   124  	default:
   125  	}
   126  	select {
   127  	case <-source.populateSuccessCh:
   128  		require.Fail(t, "no populate")
   129  	default:
   130  	}
   131  	mikePath2 = res.Picmap["mike"]["square"].String()
   132  	require.Equal(t, mikePath2, mikePath)
   133  	require.Equal(t, "hi2", getFile(mikePath2))
   134  
   135  	// load a second user to validate we clear when the LRU is full
   136  	res, err = source.LoadUsers(m, []string{"josh"}, []keybase1.AvatarFormat{"square"})
   137  	require.NoError(t, err)
   138  	require.Equal(t, testSrvAddr, res.Picmap["josh"]["square"].String())
   139  	select {
   140  	case <-cb:
   141  	case <-time.After(20 * time.Second):
   142  		require.Fail(t, "no API call")
   143  	}
   144  	select {
   145  	case <-source.populateSuccessCh:
   146  	case <-time.After(20 * time.Second):
   147  		require.Fail(t, "no populate")
   148  	}
   149  
   150  	res, err = source.LoadUsers(m, []string{"josh"}, []keybase1.AvatarFormat{"square"})
   151  	require.NoError(t, err)
   152  	select {
   153  	case <-cb:
   154  		require.Fail(t, "no API call")
   155  	default:
   156  	}
   157  	select {
   158  	case <-source.populateSuccessCh:
   159  		require.Fail(t, "no populate")
   160  	default:
   161  	}
   162  	joshPath := res.Picmap["josh"]["square"].String()
   163  	require.NotEqual(t, testSrvAddr, mikePath2)
   164  	require.True(t, strings.HasPrefix(joshPath, "file://"))
   165  	require.Equal(t, "hi2", getFile(joshPath))
   166  
   167  	// mike was evicted
   168  	_, err = os.Stat(convertPath(mikePath2))
   169  	require.Error(t, err)
   170  	require.True(t, os.IsNotExist(err))
   171  
   172  	err = source.ClearCacheForName(m, "josh", []keybase1.AvatarFormat{"square"})
   173  	require.NoError(t, err)
   174  
   175  	_, err = os.Stat(convertPath(joshPath))
   176  	require.Error(t, err)
   177  	require.True(t, os.IsNotExist(err))
   178  }