218 lines
6.6 KiB
Go
218 lines
6.6 KiB
Go
package laosiji
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type roundTripFunc func(*http.Request) (*http.Response, error)
|
|
|
|
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
return f(req)
|
|
}
|
|
|
|
func preserveConfig(t *testing.T) {
|
|
t.Helper()
|
|
old := Config{
|
|
AppID: Appid,
|
|
APIKey: APIKey,
|
|
APIUrl: APIUrl,
|
|
ImageYuan: IMAGEYUAN,
|
|
NoticeURL: NoticeURL,
|
|
}
|
|
t.Cleanup(func() {
|
|
InitConfig(old)
|
|
})
|
|
}
|
|
|
|
func TestConfiguredRejectsMissingCredentials(t *testing.T) {
|
|
preserveConfig(t)
|
|
InitConfig(Config{})
|
|
|
|
if Configured() {
|
|
t.Fatal("empty configuration must not be ready")
|
|
}
|
|
_, err := SystemDomains(context.Background())
|
|
if err == nil || !strings.Contains(err.Error(), "appId,apiKey,apiUrl") {
|
|
t.Fatalf("expected a diagnostic configuration error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMovieM3u8PathNormalization(t *testing.T) {
|
|
tests := map[string]string{
|
|
"": "",
|
|
"demo.m3u8": "laosiji/m3m/demo.m3u8",
|
|
"m3m/demo.m3u8": "laosiji/m3m/demo.m3u8",
|
|
"laosiji/m3m/demo.m3u8": "laosiji/m3m/demo.m3u8",
|
|
"https://cdn.example.com/m3m/demo.m3u8?token=ignored": "laosiji/m3m/demo.m3u8",
|
|
"https://cdn.example.com/rk130/hevc/demo/index.m3u8": "laosiji/rk130/hevc/demo/index.m3u8",
|
|
"laosiji/rk130/hevc/demo/index.m3u8?token=also-ignored": "laosiji/rk130/hevc/demo/index.m3u8",
|
|
}
|
|
for input, want := range tests {
|
|
if got := MovieM3u8SourcePath(input); got != want {
|
|
t.Errorf("MovieM3u8SourcePath(%q) = %q, want %q", input, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTranscodeTaskNoNeedIsExplicit(t *testing.T) {
|
|
sourceURL := "https://app.example/api/app/vid/h5/light/m3u8/source.m3u8"
|
|
result := transcodeTaskToResult(SystemDomainsResp{MovieSourceCDN: "https://cdn.example"}, transcodeTask{
|
|
Status: transcodeNoNeedStatus,
|
|
FileURL: sourceURL,
|
|
}, sourceURL, TranscodeFileID(sourceURL))
|
|
|
|
if !result.Done || !result.NoNeed {
|
|
t.Fatalf("status=4 must be done and no-need: %+v", result)
|
|
}
|
|
if result.HevcURL != sourceURL {
|
|
t.Fatalf("no-need diagnostic URL = %q, want %q", result.HevcURL, sourceURL)
|
|
}
|
|
}
|
|
|
|
func TestTranscodeSuccessWithoutFileIsFailed(t *testing.T) {
|
|
result := transcodeTaskToResult(SystemDomainsResp{}, transcodeTask{
|
|
Status: transcodeSuccessStatus,
|
|
}, "https://app.example/source.m3u8", "file-id")
|
|
|
|
if result.Done || !result.Failed {
|
|
t.Fatalf("empty successful task must fail locally instead of staying pending: %+v", result)
|
|
}
|
|
if !strings.Contains(result.ErrorMsg, "without transcode_file") {
|
|
t.Fatalf("missing diagnostic error: %+v", result)
|
|
}
|
|
}
|
|
|
|
func TestTranscodeFailureRedactsCredentials(t *testing.T) {
|
|
preserveConfig(t)
|
|
const (
|
|
appID = "app-id-secret"
|
|
apiKey = "api-key-secret"
|
|
uploadKey = "upload key+secret"
|
|
)
|
|
InitConfig(Config{
|
|
AppID: appID,
|
|
APIKey: apiKey,
|
|
APIUrl: "https://api.example",
|
|
})
|
|
|
|
result := transcodeTaskToResult(SystemDomainsResp{UploadKey: uploadKey}, transcodeTask{
|
|
Status: transcodeFailedStatus,
|
|
TranscodeError: fmt.Sprintf(
|
|
"failure app=%s api=%s upload=%s encoded=%s",
|
|
appID,
|
|
apiKey,
|
|
uploadKey,
|
|
url.QueryEscape(uploadKey),
|
|
),
|
|
}, "https://app.example/source.m3u8", "file-id")
|
|
|
|
if !result.Failed {
|
|
t.Fatalf("negative cloud status must be failed: %+v", result)
|
|
}
|
|
for _, secret := range []string{appID, apiKey, uploadKey, url.QueryEscape(uploadKey)} {
|
|
if strings.Contains(result.ErrorMsg, secret) {
|
|
t.Fatalf("transcode error leaked credential %q: %s", secret, result.ErrorMsg)
|
|
}
|
|
}
|
|
if !strings.Contains(result.ErrorMsg, "[REDACTED]") {
|
|
t.Fatalf("transcode error did not show redaction marker: %s", result.ErrorMsg)
|
|
}
|
|
}
|
|
|
|
func TestUploadNetworkErrorRedactsKey(t *testing.T) {
|
|
preserveConfig(t)
|
|
InitConfig(Config{
|
|
AppID: "app-id-secret",
|
|
APIKey: "1234567890abcdef",
|
|
APIUrl: "https://api.example",
|
|
})
|
|
uploadKey := "upload key+secret"
|
|
oldClient := uploadHTTPClient
|
|
uploadHTTPClient = &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
|
return nil, fmt.Errorf("dial failed for %s", req.URL.String())
|
|
})}
|
|
t.Cleanup(func() {
|
|
uploadHTTPClient = oldClient
|
|
})
|
|
|
|
_, err := uploadAPICall(context.Background(), "https://upload.example/query", map[string]string{
|
|
"key": uploadKey,
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected network error")
|
|
}
|
|
errText := err.Error()
|
|
if strings.Contains(errText, uploadKey) || strings.Contains(errText, url.QueryEscape(uploadKey)) {
|
|
t.Fatalf("network error leaked upload key: %s", errText)
|
|
}
|
|
if !strings.Contains(errText, "[REDACTED]") {
|
|
t.Fatalf("network error did not show redaction marker: %s", errText)
|
|
}
|
|
}
|
|
|
|
func TestMoviePostNon2xxIsDiagnosticAndRedacted(t *testing.T) {
|
|
preserveConfig(t)
|
|
const (
|
|
appID = "app-id-secret"
|
|
apiKey = "1234567890abcdef"
|
|
)
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusBadGateway)
|
|
_, _ = fmt.Fprintf(w, "failure app=%s key=%s %s", appID, apiKey, strings.Repeat("x", 500))
|
|
}))
|
|
defer server.Close()
|
|
InitConfig(Config{AppID: appID, APIKey: apiKey, APIUrl: server.URL})
|
|
|
|
err := moviePost(context.Background(), server.URL, map[string]interface{}{}, &struct{}{})
|
|
if err == nil || !strings.Contains(err.Error(), "status:502") {
|
|
t.Fatalf("expected HTTP status diagnostic, got %v", err)
|
|
}
|
|
if strings.Contains(err.Error(), appID) || strings.Contains(err.Error(), apiKey) {
|
|
t.Fatalf("HTTP error leaked credentials: %s", err)
|
|
}
|
|
if !strings.Contains(err.Error(), "[REDACTED]") || !strings.HasSuffix(err.Error(), "...") {
|
|
t.Fatalf("HTTP error was not safely redacted/truncated: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestQueryH264ToH265TaskUsesExplicitStableFileID(t *testing.T) {
|
|
const fileID = "stable-file-id"
|
|
oldClient := uploadHTTPClient
|
|
uploadHTTPClient = &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
|
if got := req.URL.Query().Get("file_id"); got != fileID {
|
|
t.Fatalf("query file_id = %q, want %q", got, fileID)
|
|
}
|
|
return &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Header: make(http.Header),
|
|
Body: io.NopCloser(strings.NewReader(
|
|
`{"status":"y","data":{"file_id":"stable-file-id","status":1}}`,
|
|
)),
|
|
Request: req,
|
|
}, nil
|
|
})}
|
|
t.Cleanup(func() {
|
|
uploadHTTPClient = oldClient
|
|
})
|
|
|
|
result, found, err := QueryH264ToH265Task(
|
|
context.Background(),
|
|
fileID,
|
|
"https://app.example/source.m3u8?hevc_exp=1&hevc_sig=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
SystemDomainsResp{UploadURL: "https://upload.example", UploadKey: "upload-key"},
|
|
)
|
|
if err != nil || !found {
|
|
t.Fatalf("explicit task query failed: found=%v err=%v", found, err)
|
|
}
|
|
if result.FileID != fileID {
|
|
t.Fatalf("result file ID = %q, want %q", result.FileID, fileID)
|
|
}
|
|
}
|