generated from VLADIMIR/template
add qr-s
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"mime"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"evening_detective_server/internal/modules/scenario_archive"
|
||||
"evening_detective_server/internal/modules/team_qr_pdf"
|
||||
proto "evening_detective_server/proto"
|
||||
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
@@ -78,6 +80,54 @@ func (s *testStub) getUploaded() []byte {
|
||||
return append([]byte(nil), s.uploaded...)
|
||||
}
|
||||
|
||||
func (s *testStub) DownloadTeamQR(ctx context.Context, req *proto.GetGameReq) (*httpbody.HttpBody, error) {
|
||||
if req.Id != 7 {
|
||||
return nil, status.Error(codes.NotFound, "Игра не найдена")
|
||||
}
|
||||
data, err := team_qr_pdf.NewGenerator().Generate([]team_qr_pdf.Card{{Name: "Знатоки", URL: "https://example.org/team-story/1?password=test"}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := grpc.SetHeader(ctx, metadata.Pairs(
|
||||
"Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": "2026-09-08_Дело №1.pdf"}),
|
||||
"Cache-Control", "no-store",
|
||||
)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &httpbody.HttpBody{Data: data, ContentType: "application/pdf"}, nil
|
||||
}
|
||||
|
||||
func TestGatewayDownloadTeamQR(t *testing.T) {
|
||||
ts := newTestGateway(t, &testStub{})
|
||||
resp, err := http.Get(ts.URL + "/api/games/7/teams/qr.pdf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK || resp.Header.Get("Content-Type") != "application/pdf" || !bytes.HasPrefix(data, []byte("%PDF-")) {
|
||||
t.Fatalf("response: %d, %v", resp.StatusCode, resp.Header)
|
||||
}
|
||||
mediaType, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
|
||||
if err != nil || mediaType != "attachment" || params["filename"] != "2026-09-08_Дело №1.pdf" {
|
||||
t.Fatalf("filename header: %v (%v)", resp.Header, err)
|
||||
}
|
||||
if resp.Header.Get("Cache-Control") != "no-store" {
|
||||
t.Fatal("PDF with passwords must not be cached")
|
||||
}
|
||||
resp2, err := http.Get(ts.URL + "/api/games/8/teams/qr.pdf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp2.Body.Close()
|
||||
if resp2.StatusCode != http.StatusNotFound {
|
||||
t.Fatalf("missing game: %d", resp2.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// newTestGateway поднимает gRPC-сервер со stub и grpc-gateway с теми же
|
||||
// опциями, что и в main.go (outgoing matcher, error handler, лимиты,
|
||||
// rawBodyMarshaler).
|
||||
@@ -117,6 +167,8 @@ func newTestGatewayWithLimit(t *testing.T, stub proto.EveningDetectiveServerServ
|
||||
return "Content-Disposition", true
|
||||
case strings.EqualFold(key, "X-Content-Type-Options"):
|
||||
return "X-Content-Type-Options", true
|
||||
case strings.EqualFold(key, "Cache-Control"):
|
||||
return "Cache-Control", true
|
||||
}
|
||||
return runtime.DefaultHeaderMatcher(key)
|
||||
}),
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"evening_detective_server/internal/modules/scenario_archive"
|
||||
"evening_detective_server/internal/modules/storytelling"
|
||||
"evening_detective_server/internal/modules/string_tools"
|
||||
"evening_detective_server/internal/modules/team_qr_pdf"
|
||||
"evening_detective_server/internal/repos/actions_repo"
|
||||
"evening_detective_server/internal/repos/applications_repo"
|
||||
"evening_detective_server/internal/repos/games_repo"
|
||||
@@ -139,6 +140,8 @@ func main() {
|
||||
applicationsRepo,
|
||||
answersRepo,
|
||||
cleaner,
|
||||
team_qr_pdf.NewGenerator(),
|
||||
os.Getenv("APP_BASE_URL"),
|
||||
)
|
||||
|
||||
// Create a listener on TCP port
|
||||
@@ -239,6 +242,8 @@ func main() {
|
||||
return "Content-Disposition", true
|
||||
case strings.EqualFold(key, "X-Content-Type-Options"):
|
||||
return "X-Content-Type-Options", true
|
||||
case strings.EqualFold(key, "Cache-Control"):
|
||||
return "Cache-Control", true
|
||||
}
|
||||
return runtime.DefaultHeaderMatcher(key)
|
||||
}),
|
||||
|
||||
@@ -720,6 +720,38 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/games/{id}/teams/qr.pdf": {
|
||||
"get": {
|
||||
"summary": "Скачать PDF с QR-кодами всех команд игры",
|
||||
"operationId": "EveningDetectiveServer_DownloadTeamQR",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/apiHttpBody"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Игры"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/my-scenarios": {
|
||||
"get": {
|
||||
"summary": "Получить свои сценарии",
|
||||
|
||||
Reference in New Issue
Block a user