package shunyunbao import ( "context" "errors" "io" "net/http" "net/http/httptest" "strings" "testing" "cmroubao/backend-api/internal/domain" ) func TestSessionManagerFetchProductImageUsesFixedAuthenticatedEndpoint( t *testing.T, ) { var requestPath, rawQuery, accept string manager, closeServer := imageSessionManager( t, func(writer http.ResponseWriter, request *http.Request) { requestPath = request.URL.Path rawQuery = request.URL.RawQuery accept = request.Header.Get("Accept") if cookie, err := request.Cookie("authenticated"); err != nil || cookie.Value != "yes" { t.Fatalf("image cookie = %v, %v", cookie, err) } writer.Header().Set("Content-Type", "image/png") writer.Header().Set("Content-Length", "4") _, _ = writer.Write([]byte("png!")) }, ) defer closeServer() image, err := manager.FetchProductImage(context.Background(), "190") if err != nil { t.Fatalf("FetchProductImage() error = %v", err) } defer image.Content.Close() content, err := io.ReadAll(image.Content) if err != nil { t.Fatalf("read image: %v", err) } if requestPath != ProductImagePath || rawQuery != "id=190" || image.MediaType != "image/png" || string(content) != "png!" || !strings.Contains(accept, "image/jpeg") { t.Fatalf( "image request/result = %q / %q / %q / %q / %q", requestPath, rawQuery, accept, image.MediaType, content, ) } } func TestSessionManagerFetchProductImageClassifiesFailures(t *testing.T) { manager, closeServer := imageSessionManager( t, func(writer http.ResponseWriter, request *http.Request) { switch request.URL.Query().Get("id") { case "190": writer.WriteHeader(http.StatusNotFound) case "191": writer.Header().Set("Content-Type", "application/json") _, _ = writer.Write([]byte(`{"status":false}`)) case "192": writer.Header().Set("Location", ProductImagePath+"?id=190") writer.WriteHeader(http.StatusFound) case "193": writer.Header().Set( "Content-Type", "image/jpeg", ) writer.Header().Set( "Content-Length", "20971521", ) default: writer.WriteHeader(http.StatusUnauthorized) } }, ) defer closeServer() tests := []struct { ref string want error }{ {"190", domain.ErrFreightImageNotFound}, {"191", domain.ErrFreightImageInvalid}, {"192", domain.ErrFreightSourceUnavailable}, {"193", domain.ErrFreightImageInvalid}, {"194", domain.ErrFreightSourceSessionNeeded}, } for _, test := range tests { image, err := manager.FetchProductImage( context.Background(), test.ref, ) if image.Content != nil { image.Content.Close() } if !errors.Is(err, test.want) { t.Fatalf("FetchProductImage(%q) error = %v", test.ref, err) } } if _, err := manager.FetchProductImage( context.Background(), "195", ); !errors.Is(err, domain.ErrFreightSourceSessionNeeded) { t.Fatalf("request after unauthorized error = %v", err) } } func TestSessionManagerFetchProductImageRejectsUntrustedReferences( t *testing.T, ) { manager := testSessionManager(t, "http://127.0.0.1:1", "user", "password") for _, value := range []string{ "", "0", "0190", "-1", "1.5", "https://invalid.example/image", } { if _, err := manager.FetchProductImage( context.Background(), value, ); !errors.Is(err, domain.ErrFreightImageInvalid) { t.Fatalf("FetchProductImage(%q) error = %v", value, err) } } } func imageSessionManager( t *testing.T, imageHandler http.HandlerFunc, ) (*SessionManager, func()) { t.Helper() server := httptest.NewServer(http.HandlerFunc(func( writer http.ResponseWriter, request *http.Request, ) { switch request.URL.Path { case CaptchaPath: http.SetCookie( writer, &http.Cookie{Name: "captcha", Value: "ready", Path: "/"}, ) writer.Header().Set("Content-Type", "image/png") _, _ = writer.Write([]byte("captcha")) case LoginPath: http.SetCookie( writer, &http.Cookie{ Name: "authenticated", Value: "yes", Path: "/", }, ) _, _ = writer.Write( []byte( `{"status":true,"data":{"user":{"id":1,"username":"test-user"}}}`, ), ) case UserPath: _, _ = writer.Write( []byte( `{"status":true,"data":{"id":1,"username":"test-user"}}`, ), ) case ProductImagePath: imageHandler(writer, request) default: writer.WriteHeader(http.StatusNotFound) } })) manager := testSessionManager( t, server.URL, "test-user", "test-password", ) loginForSource(t, manager) return manager, server.Close }