@@ -0,0 +1,217 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user