Files
go_vndb/vndb_test.go
2026-05-07 20:37:18 +08:00

526 lines
16 KiB
Go

package govndb
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"time"
)
func TestFieldsSkipsNilWithoutExtraComma(t *testing.T) {
t.Parallel()
got := Fields(
nil,
FieldName("title"),
nil,
FieldGroup("image", FieldName("url"), FieldName("dims")),
)
want := "title,image{url,dims}"
if got != want {
t.Fatalf("Fields() = %q, want %q", got, want)
}
}
func TestClientUsesCustomHTTPClientAndBaseURL(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Fatalf("method = %s, want %s", r.Method, http.MethodPost)
}
if r.URL.Path != "/vn" {
t.Fatalf("path = %s, want /vn", r.URL.Path)
}
if got := r.Header.Get("Authorization"); got != "Token test-token" {
t.Fatalf("authorization = %q, want %q", got, "Token test-token")
}
var request QueryRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
t.Fatalf("decode request: %v", err)
}
if request.Fields != "title" {
t.Fatalf("fields = %q, want title", request.Fields)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"results":[{"id":"v17","title":"Saya no Uta"}],"more":false}`))
}))
defer server.Close()
httpClient := &http.Client{Timeout: time.Second}
client := New(
WithHTTPClient(httpClient),
WithBaseURL(server.URL),
WithToken("test-token"),
)
response, err := Query[map[string]any](context.Background(), client, EndpointVN, QueryRequest{
Fields: "title",
Results: 1,
})
if err != nil {
t.Fatalf("Query() error = %v", err)
}
if len(response.Results) != 1 {
t.Fatalf("len(results) = %d, want 1", len(response.Results))
}
if got := response.Results[0]["title"]; got != "Saya no Uta" {
t.Fatalf("title = %v, want Saya no Uta", got)
}
}
func TestQueryUsesDefaultFieldsWhenOmitted(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var request QueryRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
t.Fatalf("decode request: %v", err)
}
if request.Fields != defaultTagFields {
t.Fatalf("fields = %q, want default tag fields", request.Fields)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"results":[{"id":"g10","name":"Romance"}],"more":false}`))
}))
defer server.Close()
client := New(WithBaseURL(server.URL))
if _, err := client.QueryTags(context.Background(), QueryRequest{}); err != nil {
t.Fatalf("QueryTags() error = %v", err)
}
}
func TestUserUsesGETQueryParameters(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Fatalf("method = %s, want %s", r.Method, http.MethodGet)
}
if r.URL.Path != "/user" {
t.Fatalf("path = %s, want /user", r.URL.Path)
}
query := r.URL.Query()
if got := query["q"]; len(got) != 2 || got[0] != "u2" || got[1] != "yorhel" {
t.Fatalf("q = %v, want [u2 yorhel]", got)
}
if got := query.Get("fields"); got != "lengthvotes" {
t.Fatalf("fields = %q, want lengthvotes", got)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"u2":{"id":"u2","username":"Yorhel"},"yorhel":{"id":"u2","username":"Yorhel"}}`))
}))
defer server.Close()
client := New(WithBaseURL(server.URL))
result, err := client.User(context.Background(), UserQuery{
Queries: []string{"u2", "yorhel"},
Fields: "lengthvotes",
})
if err != nil {
t.Fatalf("User() error = %v", err)
}
if result["u2"] == nil {
t.Fatal("result[u2] = nil, want user")
}
if got := result["u2"].Username; got != "Yorhel" {
t.Fatalf("username = %v, want Yorhel", got)
}
}
func TestUserUsesDefaultFieldsWhenOmitted(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if got := r.URL.Query().Get("fields"); got != defaultUserFields {
t.Fatalf("fields = %q, want %q", got, defaultUserFields)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"u2":{"id":"u2","username":"Yorhel"}}`))
}))
defer server.Close()
client := New(WithBaseURL(server.URL))
if _, err := client.User(context.Background(), UserQuery{Queries: []string{"u2"}}); err != nil {
t.Fatalf("User() error = %v", err)
}
}
func TestQueryVNsDecodesTypedModel(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{
"results":[{
"id":"v17",
"title":"Saya no Uta",
"image":{"id":"cv1","url":"https://example.com/image.jpg","dims":[256,400]},
"developers":[{"id":"p30","name":"Nitroplus"}],
"tags":[{"id":"g505","name":"Donkan Protagonist","rating":1.8,"spoiler":0}]
}],
"more":false
}`))
}))
defer server.Close()
client := New(WithBaseURL(server.URL))
response, err := client.QueryVNs(context.Background(), QueryRequest{
Fields: "title,image{url,dims},developers{id,name},tags{id,name,rating,spoiler}",
Results: 1,
})
if err != nil {
t.Fatalf("QueryVNs() error = %v", err)
}
if len(response.Results) != 1 {
t.Fatalf("len(results) = %d, want 1", len(response.Results))
}
vn := response.Results[0]
if vn.Title != "Saya no Uta" {
t.Fatalf("title = %q, want Saya no Uta", vn.Title)
}
if vn.Image == nil || vn.Image.Dims == nil || vn.Image.Dims[0] != 256 || vn.Image.Dims[1] != 400 {
t.Fatalf("image dims = %v, want [256 400]", vn.Image)
}
if len(vn.Developers) != 1 || vn.Developers[0].Name != "Nitroplus" {
t.Fatalf("developers = %+v, want Nitroplus", vn.Developers)
}
if len(vn.Tags) != 1 || vn.Tags[0].Name != "Donkan Protagonist" {
t.Fatalf("tags = %+v, want Donkan Protagonist", vn.Tags)
}
}
func TestQueryTagsDecodesTypedModel(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/tag" {
t.Fatalf("path = %s, want /tag", r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{
"results":[{"id":"g10","name":"Romance","category":"cont","searchable":true,"applicable":true,"vn_count":1234}],
"more":false
}`))
}))
defer server.Close()
client := New(WithBaseURL(server.URL))
response, err := client.QueryTags(context.Background(), QueryRequest{Fields: "name,category,searchable,applicable,vn_count"})
if err != nil {
t.Fatalf("QueryTags() error = %v", err)
}
if len(response.Results) != 1 {
t.Fatalf("len(results) = %d, want 1", len(response.Results))
}
tag := response.Results[0]
if tag.Name != "Romance" {
t.Fatalf("name = %q, want Romance", tag.Name)
}
if tag.Category == nil || *tag.Category != "cont" {
t.Fatalf("category = %v, want cont", tag.Category)
}
if tag.VNCount == nil || *tag.VNCount != 1234 {
t.Fatalf("vn_count = %v, want 1234", tag.VNCount)
}
}
func TestQueryQuotesDecodesTypedModel(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/quote" {
t.Fatalf("path = %s, want /quote", r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{
"results":[{
"id":"q1",
"quote":"The song of Saya.",
"score":42,
"vn":{"id":"v17","title":"Saya no Uta"},
"character":{"id":"c1","name":"Saya"}
}],
"more":false
}`))
}))
defer server.Close()
client := New(WithBaseURL(server.URL))
response, err := client.QueryQuotes(context.Background(), QueryRequest{Fields: "quote,score,vn{id,title},character{id,name}"})
if err != nil {
t.Fatalf("QueryQuotes() error = %v", err)
}
if len(response.Results) != 1 {
t.Fatalf("len(results) = %d, want 1", len(response.Results))
}
quote := response.Results[0]
if quote.Score != 42 {
t.Fatalf("score = %d, want 42", quote.Score)
}
if quote.VN == nil || quote.VN.Title != "Saya no Uta" {
t.Fatalf("vn = %+v, want Saya no Uta", quote.VN)
}
if quote.Character == nil || quote.Character.Name != "Saya" {
t.Fatalf("character = %+v, want Saya", quote.Character)
}
}
func TestUListLabelsDecodesTypedModel(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Fatalf("method = %s, want %s", r.Method, http.MethodGet)
}
if r.URL.Path != "/ulist_labels" {
t.Fatalf("path = %s, want /ulist_labels", r.URL.Path)
}
if got := r.URL.Query().Get("user"); got != "u2" {
t.Fatalf("user = %q, want u2", got)
}
if got := r.URL.Query().Get("fields"); got != "count" {
t.Fatalf("fields = %q, want count", got)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"labels":[{"id":7,"label":"Voted","private":false,"count":99}]}`))
}))
defer server.Close()
client := New(WithBaseURL(server.URL))
response, err := client.UListLabels(context.Background(), UListLabelsQuery{User: "u2", Fields: "count"})
if err != nil {
t.Fatalf("UListLabels() error = %v", err)
}
if len(response.Labels) != 1 {
t.Fatalf("len(labels) = %d, want 1", len(response.Labels))
}
label := response.Labels[0]
if label.Label != "Voted" {
t.Fatalf("label = %q, want Voted", label.Label)
}
if label.Count == nil || *label.Count != 99 {
t.Fatalf("count = %v, want 99", label.Count)
}
}
func TestQueryReleasesDecodesPolymorphicTypedFields(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/release" {
t.Fatalf("path = %s, want /release", r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{
"results":[{
"id":"r12",
"title":"Saya no Uta DVD",
"resolution":[800,600],
"extlinks":[{"url":"https://store.example/app/12","name":"steam","id":12}]
}],
"more":false
}`))
}))
defer server.Close()
client := New(WithBaseURL(server.URL))
response, err := client.QueryReleases(context.Background(), QueryRequest{Fields: "title,resolution,extlinks{id,url,name}"})
if err != nil {
t.Fatalf("QueryReleases() error = %v", err)
}
if len(response.Results) != 1 {
t.Fatalf("len(results) = %d, want 1", len(response.Results))
}
release := response.Results[0]
if release.Resolution == nil || release.Resolution.Width != 800 || release.Resolution.Height != 600 || release.Resolution.NonStandard {
t.Fatalf("resolution = %+v, want 800x600", release.Resolution)
}
if len(release.ExtLinks) != 1 || release.ExtLinks[0].ID == nil || release.ExtLinks[0].ID.Int == nil || *release.ExtLinks[0].ID.Int != 12 {
t.Fatalf("extlinks = %+v, want int id 12", release.ExtLinks)
}
if release.ExtLinks[0].ID.String != nil {
t.Fatalf("extlink string id = %v, want nil", *release.ExtLinks[0].ID.String)
}
}
func TestGetVNUsesCoreFieldsAndIDFilter(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/vn" {
t.Fatalf("path = %s, want /vn", r.URL.Path)
}
var request QueryRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
t.Fatalf("decode request: %v", err)
}
if request.Fields != getVNFields {
t.Fatalf("fields = %q, want %q", request.Fields, getVNFields)
}
wantFilter := []any{"id", "=", "v17"}
if !reflect.DeepEqual(request.Filters, wantFilter) {
t.Fatalf("filters = %#v, want %#v", request.Filters, wantFilter)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"results":[{"id":"v17","title":"Saya no Uta","developers":[{"id":"p30","name":"Nitroplus"}]}],"more":false}`))
}))
defer server.Close()
client := New(WithBaseURL(server.URL))
vn, err := client.GetVN(context.Background(), "v17")
if err != nil {
t.Fatalf("GetVN() error = %v", err)
}
if vn == nil || vn.ID != "v17" || vn.Title != "Saya no Uta" {
t.Fatalf("GetVN() = %+v, want v17/Saya no Uta", vn)
}
}
func TestGetVNRatingUsesRatingOnlyField(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/vn" {
t.Fatalf("path = %s, want /vn", r.URL.Path)
}
var request QueryRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
t.Fatalf("decode request: %v", err)
}
if request.Fields != getVNRatingFields {
t.Fatalf("fields = %q, want %q", request.Fields, getVNRatingFields)
}
wantFilter := []any{"id", "=", "v17"}
if !reflect.DeepEqual(request.Filters, wantFilter) {
t.Fatalf("filters = %#v, want %#v", request.Filters, wantFilter)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"results":[{"id":"v17","rating":83.5}],"more":false}`))
}))
defer server.Close()
client := New(WithBaseURL(server.URL))
rating, err := client.GetVNRating(context.Background(), "v17")
if err != nil {
t.Fatalf("GetVNRating() error = %v", err)
}
if rating == nil || *rating != 83.5 {
t.Fatalf("GetVNRating() = %v, want 83.5", rating)
}
}
func TestGetVNRatingReturnsNilWhenUnavailable(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"results":[{"id":"v17","rating":null}],"more":false}`))
}))
defer server.Close()
client := New(WithBaseURL(server.URL))
rating, err := client.GetVNRating(context.Background(), "v17")
if err != nil {
t.Fatalf("GetVNRating() error = %v", err)
}
if rating != nil {
t.Fatalf("GetVNRating() = %v, want nil", *rating)
}
}
func TestGetVNCharactersUsesVNFilter(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/character" {
t.Fatalf("path = %s, want /character", r.URL.Path)
}
var request QueryRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
t.Fatalf("decode request: %v", err)
}
if request.Fields != getVNCharactersFields {
t.Fatalf("fields = %q, want %q", request.Fields, getVNCharactersFields)
}
wantFilter := []any{"vn", "=", []any{"id", "=", "v17"}}
if !reflect.DeepEqual(request.Filters, wantFilter) {
t.Fatalf("filters = %#v, want %#v", request.Filters, wantFilter)
}
if request.Results != 100 {
t.Fatalf("results = %d, want 100", request.Results)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"results":[{"id":"c1","name":"Saya","vns":[{"role":"main"}]}],"more":false}`))
}))
defer server.Close()
client := New(WithBaseURL(server.URL))
characters, err := client.GetVNCharacters(context.Background(), "v17")
if err != nil {
t.Fatalf("GetVNCharacters() error = %v", err)
}
if len(characters) != 1 || characters[0].Name != "Saya" {
t.Fatalf("GetVNCharacters() = %+v, want Saya", characters)
}
}
func TestSearchVNUsesSearchFilter(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/vn" {
t.Fatalf("path = %s, want /vn", r.URL.Path)
}
var request QueryRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
t.Fatalf("decode request: %v", err)
}
if request.Fields != searchVNFields {
t.Fatalf("fields = %q, want %q", request.Fields, searchVNFields)
}
wantFilter := []any{"search", "=", "saya"}
if !reflect.DeepEqual(request.Filters, wantFilter) {
t.Fatalf("filters = %#v, want %#v", request.Filters, wantFilter)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"results":[{"id":"v17","title":"Saya no Uta","alttitle":"沙耶の唄"}],"more":false}`))
}))
defer server.Close()
client := New(WithBaseURL(server.URL))
vns, err := client.SearchVN(context.Background(), "saya")
if err != nil {
t.Fatalf("SearchVN() error = %v", err)
}
if len(vns) != 1 || vns[0].ID != "v17" || vns[0].Title != "Saya no Uta" {
t.Fatalf("SearchVN() = %+v, want v17/Saya no Uta", vns)
}
}