2020-01-02 02:12:45 +05:30
package http
import (
2024-02-07 06:06:18 +05:30
"context"
2021-02-26 07:15:45 +05:30
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
2024-01-30 13:35:04 +05:30
"io"
2020-01-02 02:12:45 +05:30
"net/http"
"net/http/httptest"
2021-03-03 18:03:56 +05:30
"strings"
2020-01-02 02:12:45 +05:30
"testing"
"time"
"github.com/stretchr/testify/mock"
2024-01-30 13:35:04 +05:30
testify "github.com/stretchr/testify/require"
2020-01-02 02:12:45 +05:30
"github.com/stretchr/testify/suite"
2024-02-01 12:42:34 +05:30
"ely.by/chrly/internal/db"
2020-01-02 02:12:45 +05:30
)
2024-01-30 13:35:04 +05:30
type ProfilesProviderMock struct {
2020-01-02 02:12:45 +05:30
mock . Mock
}
2024-02-07 06:06:18 +05:30
func ( m * ProfilesProviderMock ) FindProfileByUsername ( ctx context . Context , username string , allowProxy bool ) ( * db . Profile , error ) {
args := m . Called ( ctx , username , allowProxy )
2024-01-30 13:35:04 +05:30
var result * db . Profile
if casted , ok := args . Get ( 0 ) . ( * db . Profile ) ; ok {
2020-01-02 02:12:45 +05:30
result = casted
}
return result , args . Error ( 1 )
}
2024-01-30 13:35:04 +05:30
type TexturesSignerMock struct {
2021-02-26 07:15:45 +05:30
mock . Mock
}
2024-01-30 13:35:04 +05:30
func ( m * TexturesSignerMock ) SignTextures ( textures string ) ( string , error ) {
2021-02-26 07:15:45 +05:30
args := m . Called ( textures )
return args . String ( 0 ) , args . Error ( 1 )
}
2024-01-30 13:35:04 +05:30
func ( m * TexturesSignerMock ) GetPublicKey ( ) ( * rsa . PublicKey , error ) {
2021-02-26 07:15:45 +05:30
args := m . Called ( )
var publicKey * rsa . PublicKey
if casted , ok := args . Get ( 0 ) . ( * rsa . PublicKey ) ; ok {
publicKey = casted
}
return publicKey , args . Error ( 1 )
}
2024-01-30 13:35:04 +05:30
type SkinsystemTestSuite struct {
2020-01-02 02:12:45 +05:30
suite . Suite
App * Skinsystem
2024-01-30 13:35:04 +05:30
ProfilesProvider * ProfilesProviderMock
TexturesSigner * TexturesSignerMock
2020-01-02 02:12:45 +05:30
}
/ * * * * * * * * * * * * * * * * * * * *
* Setup test suite *
* * * * * * * * * * * * * * * * * * * * /
2024-01-30 13:35:04 +05:30
func ( t * SkinsystemTestSuite ) SetupSubTest ( ) {
2021-02-26 07:15:45 +05:30
timeNow = func ( ) time . Time {
CET , _ := time . LoadLocation ( "CET" )
return time . Date ( 2021 , 02 , 25 , 01 , 50 , 23 , 0 , CET )
}
2024-01-30 13:35:04 +05:30
t . ProfilesProvider = & ProfilesProviderMock { }
t . TexturesSigner = & TexturesSignerMock { }
t . App = & Skinsystem {
ProfilesProvider : t . ProfilesProvider ,
TexturesSigner : t . TexturesSigner ,
2020-04-20 19:52:04 +05:30
TexturesExtraParamName : "texturesParamName" ,
TexturesExtraParamValue : "texturesParamValue" ,
2020-01-02 02:12:45 +05:30
}
}
2024-01-30 13:35:04 +05:30
func ( t * SkinsystemTestSuite ) TearDownSubTest ( ) {
t . ProfilesProvider . AssertExpectations ( t . T ( ) )
t . TexturesSigner . AssertExpectations ( t . T ( ) )
2020-01-02 02:12:45 +05:30
}
2024-01-30 13:35:04 +05:30
func ( t * SkinsystemTestSuite ) TestSkinHandler ( ) {
for _ , url := range [ ] string { "http://chrly/skins/mock_username" , "http://chrly/skins?name=mock_username" } {
t . Run ( "known username with a skin" , func ( ) {
req := httptest . NewRequest ( "GET" , url , nil )
w := httptest . NewRecorder ( )
2020-01-02 02:12:45 +05:30
2024-02-07 06:06:18 +05:30
// TODO: see the TODO about context above
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( & db . Profile {
SkinUrl : "https://example.com/skin.png" ,
} , nil )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
result := w . Result ( )
t . Equal ( http . StatusMovedPermanently , result . StatusCode )
t . Equal ( "https://example.com/skin.png" , result . Header . Get ( "Location" ) )
} )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
t . Run ( "known username without a skin" , func ( ) {
req := httptest . NewRequest ( "GET" , url , nil )
w := httptest . NewRecorder ( )
2020-01-02 02:12:45 +05:30
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( & db . Profile { } , nil )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
result := w . Result ( )
t . Equal ( http . StatusNotFound , result . StatusCode )
} )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
t . Run ( "err from profiles provider" , func ( ) {
req := httptest . NewRequest ( "GET" , url , nil )
2020-01-02 02:12:45 +05:30
w := httptest . NewRecorder ( )
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( nil , errors . New ( "mock error" ) )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
result := w . Result ( )
t . Equal ( http . StatusInternalServerError , result . StatusCode )
2020-01-02 02:12:45 +05:30
} )
}
2024-01-30 13:35:04 +05:30
t . Run ( "username with png extension" , func ( ) {
2020-01-02 02:12:45 +05:30
req := httptest . NewRequest ( "GET" , "http://chrly/skins/mock_username.png" , nil )
w := httptest . NewRecorder ( )
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( & db . Profile {
SkinUrl : "https://example.com/skin.png" ,
} , nil )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
result := w . Result ( )
t . Equal ( http . StatusMovedPermanently , result . StatusCode )
t . Equal ( "https://example.com/skin.png" , result . Header . Get ( "Location" ) )
} )
t . Run ( "no name param" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/skins" , nil )
w := httptest . NewRecorder ( )
t . App . Handler ( ) . ServeHTTP ( w , req )
2020-01-02 02:12:45 +05:30
resp := w . Result ( )
2024-01-30 13:35:04 +05:30
t . Equal ( http . StatusBadRequest , resp . StatusCode )
2020-01-02 02:12:45 +05:30
} )
}
2024-01-30 13:35:04 +05:30
func ( t * SkinsystemTestSuite ) TestCapeHandler ( ) {
for _ , url := range [ ] string { "http://chrly/cloaks/mock_username" , "http://chrly/cloaks?name=mock_username" } {
t . Run ( "known username with a skin" , func ( ) {
req := httptest . NewRequest ( "GET" , url , nil )
w := httptest . NewRecorder ( )
2024-02-07 06:06:18 +05:30
// TODO: I can't find a way to verify that it's the context from the request that was passed in,
// as the Mux calls WithValue() on it, which creates a new Context and I haven't been able
// to find a way to verify that the passed context matches the base
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( & db . Profile {
CapeUrl : "https://example.com/cape.png" ,
} , nil )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
result := w . Result ( )
t . Equal ( http . StatusMovedPermanently , result . StatusCode )
t . Equal ( "https://example.com/cape.png" , result . Header . Get ( "Location" ) )
} )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
t . Run ( "known username without a skin" , func ( ) {
req := httptest . NewRequest ( "GET" , url , nil )
2020-01-02 02:12:45 +05:30
w := httptest . NewRecorder ( )
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( & db . Profile { } , nil )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
result := w . Result ( )
t . Equal ( http . StatusNotFound , result . StatusCode )
} )
t . Run ( "err from profiles provider" , func ( ) {
req := httptest . NewRequest ( "GET" , url , nil )
w := httptest . NewRecorder ( )
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( nil , errors . New ( "mock error" ) )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
result := w . Result ( )
t . Equal ( http . StatusInternalServerError , result . StatusCode )
2020-01-02 02:12:45 +05:30
} )
}
2024-01-30 13:35:04 +05:30
t . Run ( "username with png extension" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/cloaks/mock_username.png" , nil )
w := httptest . NewRecorder ( )
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( & db . Profile {
CapeUrl : "https://example.com/cape.png" ,
} , nil )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
result := w . Result ( )
t . Equal ( http . StatusMovedPermanently , result . StatusCode )
t . Equal ( "https://example.com/cape.png" , result . Header . Get ( "Location" ) )
} )
t . Run ( "no name param" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/cloaks" , nil )
2020-01-02 02:12:45 +05:30
w := httptest . NewRecorder ( )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
2020-01-02 02:12:45 +05:30
resp := w . Result ( )
2024-01-30 13:35:04 +05:30
t . Equal ( http . StatusBadRequest , resp . StatusCode )
2020-01-02 02:12:45 +05:30
} )
}
2024-01-30 13:35:04 +05:30
func ( t * SkinsystemTestSuite ) TestTexturesHandler ( ) {
t . Run ( "known username with both textures" , func ( ) {
2024-02-07 06:06:18 +05:30
req := httptest . NewRequest ( "GET" , "http://chrly/textures/mock_username" , nil )
w := httptest . NewRecorder ( )
// TODO: see the TODO about context above
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( & db . Profile {
2024-01-30 13:35:04 +05:30
SkinUrl : "https://example.com/skin.png" ,
CapeUrl : "https://example.com/cape.png" ,
} , nil )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
result := w . Result ( )
t . Equal ( http . StatusOK , result . StatusCode )
t . Equal ( "application/json" , result . Header . Get ( "Content-Type" ) )
body , _ := io . ReadAll ( result . Body )
t . JSONEq ( ` {
"SKIN" : {
"url" : "https://example.com/skin.png"
} ,
"CAPE" : {
"url" : "https://example.com/cape.png"
}
} ` , string ( body ) )
} )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
t . Run ( "known username with only slim skin" , func ( ) {
2024-02-07 06:06:18 +05:30
req := httptest . NewRequest ( "GET" , "http://chrly/textures/mock_username" , nil )
w := httptest . NewRecorder ( )
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( & db . Profile {
2024-01-30 13:35:04 +05:30
SkinUrl : "https://example.com/skin.png" ,
SkinModel : "slim" ,
} , nil )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
result := w . Result ( )
body , _ := io . ReadAll ( result . Body )
t . JSONEq ( ` {
"SKIN" : {
"url" : "https://example.com/skin.png" ,
"metadata" : {
"model" : "slim"
}
2021-02-26 07:15:45 +05:30
}
2024-01-30 13:35:04 +05:30
} ` , string ( body ) )
} )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
t . Run ( "known username with only cape" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/textures/mock_username" , nil )
w := httptest . NewRecorder ( )
2020-01-02 02:12:45 +05:30
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( & db . Profile {
CapeUrl : "https://example.com/cape.png" ,
} , nil )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
result := w . Result ( )
body , _ := io . ReadAll ( result . Body )
t . JSONEq ( ` {
"CAPE" : {
"url" : "https://example.com/cape.png"
}
} ` , string ( body ) )
} )
t . Run ( "known username without any textures" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/textures/mock_username" , nil )
2020-01-02 02:12:45 +05:30
w := httptest . NewRecorder ( )
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( & db . Profile { } , nil )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
result := w . Result ( )
t . Equal ( http . StatusNoContent , result . StatusCode )
body , _ := io . ReadAll ( result . Body )
t . Empty ( body )
2020-01-02 02:12:45 +05:30
} )
2024-01-30 13:35:04 +05:30
t . Run ( "unknown username" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/textures/mock_username" , nil )
w := httptest . NewRecorder ( )
2020-01-02 02:12:45 +05:30
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( nil , nil )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
result := w . Result ( )
t . Equal ( http . StatusNotFound , result . StatusCode )
body , _ := io . ReadAll ( result . Body )
t . Empty ( body )
} )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
t . Run ( "err from profiles provider" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/textures/mock_username" , nil )
2020-01-02 02:12:45 +05:30
w := httptest . NewRecorder ( )
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( nil , errors . New ( "mock error" ) )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
result := w . Result ( )
t . Equal ( http . StatusInternalServerError , result . StatusCode )
2020-01-02 02:12:45 +05:30
} )
}
2024-01-30 13:35:04 +05:30
func ( t * SkinsystemTestSuite ) TestSignedTextures ( ) {
t . Run ( "exists profile with mojang textures" , func ( ) {
2024-02-07 06:06:18 +05:30
req := httptest . NewRequest ( "GET" , "http://chrly/textures/signed/mock_username" , nil )
w := httptest . NewRecorder ( )
// TODO: see the TODO about context above
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , false ) . Return ( & db . Profile {
2024-01-30 13:35:04 +05:30
Uuid : "mock-uuid" ,
Username : "mock" ,
MojangTextures : "mock-mojang-textures" ,
MojangSignature : "mock-mojang-signature" ,
} , nil )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
result := w . Result ( )
t . Equal ( http . StatusOK , result . StatusCode )
t . Equal ( "application/json" , result . Header . Get ( "Content-Type" ) )
body , _ := io . ReadAll ( result . Body )
t . JSONEq ( ` {
"id" : "mock-uuid" ,
"name" : "mock" ,
"properties" : [
{
"name" : "textures" ,
"signature" : "mock-mojang-signature" ,
"value" : "mock-mojang-textures"
2020-01-02 02:12:45 +05:30
} ,
2024-01-30 13:35:04 +05:30
{
"name" : "texturesParamName" ,
"value" : "texturesParamValue"
2020-01-02 02:12:45 +05:30
}
2024-01-30 13:35:04 +05:30
]
} ` , string ( body ) )
} )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
t . Run ( "exists profile without mojang textures" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/textures/signed/mock_username" , nil )
w := httptest . NewRecorder ( )
2020-01-02 02:12:45 +05:30
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , false ) . Return ( & db . Profile { } , nil )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
result := w . Result ( )
t . Equal ( http . StatusNoContent , result . StatusCode )
body , _ := io . ReadAll ( result . Body )
t . Empty ( body )
} )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
t . Run ( "not exists profile" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/textures/signed/mock_username" , nil )
w := httptest . NewRecorder ( )
2020-01-02 02:12:45 +05:30
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , false ) . Return ( nil , nil )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
result := w . Result ( )
t . Equal ( http . StatusNotFound , result . StatusCode )
body , _ := io . ReadAll ( result . Body )
t . Empty ( body )
} )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
t . Run ( "err from profiles provider" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/textures/signed/mock_username" , nil )
w := httptest . NewRecorder ( )
2020-01-02 02:12:45 +05:30
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , false ) . Return ( nil , errors . New ( "mock error" ) )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
result := w . Result ( )
t . Equal ( http . StatusInternalServerError , result . StatusCode )
} )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
t . Run ( "should allow proxying when specified get param" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/textures/signed/mock_username?proxy=true" , nil )
w := httptest . NewRecorder ( )
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( nil , nil )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
} )
2021-02-26 07:15:45 +05:30
}
2024-01-30 13:35:04 +05:30
func ( t * SkinsystemTestSuite ) TestProfile ( ) {
t . Run ( "exists profile with skin and cape" , func ( ) {
2024-02-07 06:06:18 +05:30
req := httptest . NewRequest ( "GET" , "http://chrly/profile/mock_username" , nil )
w := httptest . NewRecorder ( )
// TODO: see the TODO about context above
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( & db . Profile {
2024-01-30 13:35:04 +05:30
Uuid : "mock-uuid" ,
Username : "mock_username" ,
SkinUrl : "https://example.com/skin.png" ,
SkinModel : "slim" ,
CapeUrl : "https://example.com/cape.png" ,
} , nil )
2020-01-02 02:12:45 +05:30
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
result := w . Result ( )
t . Equal ( http . StatusOK , result . StatusCode )
t . Equal ( "application/json" , result . Header . Get ( "Content-Type" ) )
body , _ := io . ReadAll ( result . Body )
t . JSONEq ( ` {
"id" : "mock-uuid" ,
"name" : "mock_username" ,
"properties" : [
{
"name" : "textures" ,
"value" : "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6Im1vY2stdXVpZCIsInByb2ZpbGVOYW1lIjoibW9ja191c2VybmFtZSIsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9za2luLnBuZyIsIm1ldGFkYXRhIjp7Im1vZGVsIjoic2xpbSJ9fSwiQ0FQRSI6eyJ1cmwiOiJodHRwczovL2V4YW1wbGUuY29tL2NhcGUucG5nIn19fQ=="
} ,
{
"name" : "texturesParamName" ,
"value" : "texturesParamValue"
}
]
} ` , string ( body ) )
} )
2021-02-26 07:15:45 +05:30
2024-01-30 13:35:04 +05:30
t . Run ( "exists signed profile with skin" , func ( ) {
2024-02-07 06:06:18 +05:30
req := httptest . NewRequest ( "GET" , "http://chrly/profile/mock_username?unsigned=false" , nil )
w := httptest . NewRecorder ( )
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( & db . Profile {
2024-01-30 13:35:04 +05:30
Uuid : "mock-uuid" ,
Username : "mock_username" ,
SkinUrl : "https://example.com/skin.png" ,
SkinModel : "slim" ,
} , nil )
t . TexturesSigner . On ( "SignTextures" , "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6Im1vY2stdXVpZCIsInByb2ZpbGVOYW1lIjoibW9ja191c2VybmFtZSIsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9za2luLnBuZyIsIm1ldGFkYXRhIjp7Im1vZGVsIjoic2xpbSJ9fX19" ) . Return ( "mock signature" , nil )
2021-02-26 07:15:45 +05:30
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
result := w . Result ( )
t . Equal ( http . StatusOK , result . StatusCode )
t . Equal ( "application/json" , result . Header . Get ( "Content-Type" ) )
body , _ := io . ReadAll ( result . Body )
t . JSONEq ( ` {
"id" : "mock-uuid" ,
"name" : "mock_username" ,
"properties" : [
{
"name" : "textures" ,
"signature" : "mock signature" ,
"value" : "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6Im1vY2stdXVpZCIsInByb2ZpbGVOYW1lIjoibW9ja191c2VybmFtZSIsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9za2luLnBuZyIsIm1ldGFkYXRhIjp7Im1vZGVsIjoic2xpbSJ9fX19"
} ,
{
"name" : "texturesParamName" ,
"value" : "texturesParamValue"
}
]
} ` , string ( body ) )
} )
2021-02-26 07:15:45 +05:30
2024-01-30 13:35:04 +05:30
t . Run ( "not exists profile" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/profile/mock_username" , nil )
w := httptest . NewRecorder ( )
2021-02-26 07:15:45 +05:30
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( nil , nil )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
2021-02-26 07:15:45 +05:30
2024-01-30 13:35:04 +05:30
result := w . Result ( )
t . Equal ( http . StatusNotFound , result . StatusCode )
body , _ := io . ReadAll ( result . Body )
t . Empty ( body )
} )
t . Run ( "err from profiles provider" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/profile/mock_username" , nil )
w := httptest . NewRecorder ( )
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( nil , errors . New ( "mock error" ) )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
result := w . Result ( )
t . Equal ( http . StatusInternalServerError , result . StatusCode )
} )
t . Run ( "err from textures signer" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/profile/mock_username?unsigned=false" , nil )
w := httptest . NewRecorder ( )
2021-02-26 07:15:45 +05:30
2024-02-07 06:06:18 +05:30
t . ProfilesProvider . On ( "FindProfileByUsername" , mock . Anything , "mock_username" , true ) . Return ( & db . Profile { } , nil )
t . TexturesSigner . On ( "SignTextures" , mock . Anything ) . Return ( "" , errors . New ( "mock error" ) )
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
result := w . Result ( )
t . Equal ( http . StatusInternalServerError , result . StatusCode )
} )
}
2021-02-26 07:15:45 +05:30
2021-03-03 18:03:56 +05:30
type signingKeyTestCase struct {
Name string
KeyFormat string
2024-01-30 13:35:04 +05:30
BeforeTest func ( suite * SkinsystemTestSuite )
2021-03-03 18:03:56 +05:30
PanicErr string
2024-01-30 13:35:04 +05:30
AfterTest func ( suite * SkinsystemTestSuite , response * http . Response )
2021-03-03 18:03:56 +05:30
}
var signingKeyTestsCases = [ ] * signingKeyTestCase {
2021-02-26 07:15:45 +05:30
{
2021-03-03 18:03:56 +05:30
Name : "Get public key in DER format" ,
KeyFormat : "DER" ,
2024-01-30 13:35:04 +05:30
BeforeTest : func ( suite * SkinsystemTestSuite ) {
2021-02-26 07:15:45 +05:30
pubPem , _ := pem . Decode ( [ ] byte ( "-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnm\nUDlzHBQH3DpYef5WCO32TDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQ==\n-----END PUBLIC KEY-----" ) )
publicKey , _ := x509 . ParsePKIXPublicKey ( pubPem . Bytes )
suite . TexturesSigner . On ( "GetPublicKey" ) . Return ( publicKey , nil )
} ,
2024-01-30 13:35:04 +05:30
AfterTest : func ( suite * SkinsystemTestSuite , response * http . Response ) {
2021-02-26 07:15:45 +05:30
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/octet-stream" , response . Header . Get ( "Content-Type" ) )
2021-03-03 18:03:56 +05:30
suite . Equal ( "attachment; filename=\"yggdrasil_session_pubkey.der\"" , response . Header . Get ( "Content-Disposition" ) )
2024-01-30 13:35:04 +05:30
body , _ := io . ReadAll ( response . Body )
2021-02-26 07:15:45 +05:30
suite . Equal ( [ ] byte { 48 , 92 , 48 , 13 , 6 , 9 , 42 , 134 , 72 , 134 , 247 , 13 , 1 , 1 , 1 , 5 , 0 , 3 , 75 , 0 , 48 , 72 , 2 , 65 , 0 , 214 , 212 , 165 , 80 , 153 , 144 , 194 , 169 , 126 , 246 , 25 , 211 , 197 , 183 , 150 , 233 , 157 , 1 , 166 , 49 , 44 , 25 , 230 , 80 , 57 , 115 , 28 , 20 , 7 , 220 , 58 , 88 , 121 , 254 , 86 , 8 , 237 , 246 , 76 , 53 , 58 , 125 , 226 , 9 , 231 , 192 , 52 , 148 , 12 , 176 , 130 , 214 , 120 , 195 , 8 , 182 , 116 , 97 , 206 , 207 , 253 , 97 , 2 , 247 , 2 , 3 , 1 , 0 , 1 } , body )
} ,
} ,
{
2021-03-03 18:03:56 +05:30
Name : "Get public key in PEM format" ,
KeyFormat : "PEM" ,
2024-01-30 13:35:04 +05:30
BeforeTest : func ( suite * SkinsystemTestSuite ) {
2021-03-03 18:03:56 +05:30
pubPem , _ := pem . Decode ( [ ] byte ( "-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnm\nUDlzHBQH3DpYef5WCO32TDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQ==\n-----END PUBLIC KEY-----" ) )
publicKey , _ := x509 . ParsePKIXPublicKey ( pubPem . Bytes )
suite . TexturesSigner . On ( "GetPublicKey" ) . Return ( publicKey , nil )
} ,
2024-01-30 13:35:04 +05:30
AfterTest : func ( suite * SkinsystemTestSuite , response * http . Response ) {
2021-03-03 18:03:56 +05:30
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "text/plain; charset=utf-8" , response . Header . Get ( "Content-Type" ) )
suite . Equal ( "attachment; filename=\"yggdrasil_session_pubkey.pem\"" , response . Header . Get ( "Content-Disposition" ) )
2024-01-30 13:35:04 +05:30
body , _ := io . ReadAll ( response . Body )
2021-03-03 18:03:56 +05:30
suite . Equal ( "-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnm\nUDlzHBQH3DpYef5WCO32TDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQ==\n-----END PUBLIC KEY-----\n" , string ( body ) )
} ,
} ,
{
Name : "Error while obtaining public key" ,
KeyFormat : "DER" ,
2024-01-30 13:35:04 +05:30
BeforeTest : func ( suite * SkinsystemTestSuite ) {
2021-02-26 07:15:45 +05:30
suite . TexturesSigner . On ( "GetPublicKey" ) . Return ( nil , errors . New ( "textures signer error" ) )
} ,
PanicErr : "textures signer error" ,
} ,
}
2024-01-30 13:35:04 +05:30
func ( t * SkinsystemTestSuite ) TestSignatureVerificationKey ( ) {
2021-02-26 07:15:45 +05:30
for _ , testCase := range signingKeyTestsCases {
2024-01-30 13:35:04 +05:30
t . Run ( testCase . Name , func ( ) {
testCase . BeforeTest ( t )
2021-02-26 07:15:45 +05:30
2021-03-03 18:03:56 +05:30
req := httptest . NewRequest ( "GET" , "http://chrly/signature-verification-key." + strings . ToLower ( testCase . KeyFormat ) , nil )
2021-02-26 07:15:45 +05:30
w := httptest . NewRecorder ( )
if testCase . PanicErr != "" {
2024-01-30 13:35:04 +05:30
t . PanicsWithError ( testCase . PanicErr , func ( ) {
t . App . Handler ( ) . ServeHTTP ( w , req )
2021-02-26 07:15:45 +05:30
} )
} else {
2024-01-30 13:35:04 +05:30
t . App . Handler ( ) . ServeHTTP ( w , req )
testCase . AfterTest ( t , w . Result ( ) )
2021-02-26 07:15:45 +05:30
}
2020-01-02 02:12:45 +05:30
} )
}
}
2024-01-30 13:35:04 +05:30
func TestSkinsystem ( t * testing . T ) {
suite . Run ( t , new ( SkinsystemTestSuite ) )
}
2020-01-02 02:12:45 +05:30
func TestParseUsername ( t * testing . T ) {
assert := testify . New ( t )
assert . Equal ( "test" , parseUsername ( "test.png" ) , "Function should trim .png at end" )
assert . Equal ( "test" , parseUsername ( "test" ) , "Function should return string itself, if it not contains .png at end" )
}