2020-01-02 02:12:45 +05:30
package http
import (
"bytes"
2021-02-26 07:15:45 +05:30
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
2020-01-02 02:12:45 +05:30
"image"
"image/png"
"io/ioutil"
"net/http"
"net/http/httptest"
2021-03-03 18:03:56 +05:30
"strings"
2020-01-02 02:12:45 +05:30
"testing"
"time"
2020-01-29 04:04:15 +05:30
testify "github.com/stretchr/testify/assert"
2020-01-02 02:12:45 +05:30
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"github.com/elyby/chrly/api/mojang"
"github.com/elyby/chrly/model"
)
/ * * * * * * * * * * * * * * *
* Setup mocks *
* * * * * * * * * * * * * * * /
type skinsRepositoryMock struct {
mock . Mock
}
2020-04-21 00:48:27 +05:30
func ( m * skinsRepositoryMock ) FindSkinByUsername ( username string ) ( * model . Skin , error ) {
2020-01-02 02:12:45 +05:30
args := m . Called ( username )
var result * model . Skin
if casted , ok := args . Get ( 0 ) . ( * model . Skin ) ; ok {
result = casted
}
return result , args . Error ( 1 )
}
2020-04-21 00:48:27 +05:30
func ( m * skinsRepositoryMock ) FindSkinByUserId ( id int ) ( * model . Skin , error ) {
2020-01-02 02:12:45 +05:30
args := m . Called ( id )
var result * model . Skin
if casted , ok := args . Get ( 0 ) . ( * model . Skin ) ; ok {
result = casted
}
return result , args . Error ( 1 )
}
2020-04-21 00:48:27 +05:30
func ( m * skinsRepositoryMock ) SaveSkin ( skin * model . Skin ) error {
2020-01-02 02:12:45 +05:30
args := m . Called ( skin )
return args . Error ( 0 )
}
2020-04-21 00:48:27 +05:30
func ( m * skinsRepositoryMock ) RemoveSkinByUserId ( id int ) error {
2020-01-02 02:12:45 +05:30
args := m . Called ( id )
return args . Error ( 0 )
}
2020-04-21 00:48:27 +05:30
func ( m * skinsRepositoryMock ) RemoveSkinByUsername ( username string ) error {
2020-01-02 02:12:45 +05:30
args := m . Called ( username )
return args . Error ( 0 )
}
type capesRepositoryMock struct {
mock . Mock
}
2020-04-21 00:48:27 +05:30
func ( m * capesRepositoryMock ) FindCapeByUsername ( username string ) ( * model . Cape , error ) {
2020-01-02 02:12:45 +05:30
args := m . Called ( username )
var result * model . Cape
if casted , ok := args . Get ( 0 ) . ( * model . Cape ) ; ok {
result = casted
}
return result , args . Error ( 1 )
}
type mojangTexturesProviderMock struct {
mock . Mock
}
func ( m * mojangTexturesProviderMock ) GetForUsername ( username string ) ( * mojang . SignedTexturesResponse , error ) {
args := m . Called ( username )
var result * mojang . SignedTexturesResponse
if casted , ok := args . Get ( 0 ) . ( * mojang . SignedTexturesResponse ) ; ok {
result = casted
}
return result , args . Error ( 1 )
}
2021-02-26 07:15:45 +05:30
type texturesSignerMock struct {
mock . Mock
}
func ( m * texturesSignerMock ) SignTextures ( textures string ) ( string , error ) {
args := m . Called ( textures )
return args . String ( 0 ) , args . Error ( 1 )
}
func ( m * texturesSignerMock ) GetPublicKey ( ) ( * rsa . PublicKey , error ) {
args := m . Called ( )
var publicKey * rsa . PublicKey
if casted , ok := args . Get ( 0 ) . ( * rsa . PublicKey ) ; ok {
publicKey = casted
}
return publicKey , args . Error ( 1 )
}
2020-01-02 02:12:45 +05:30
type skinsystemTestSuite struct {
suite . Suite
App * Skinsystem
SkinsRepository * skinsRepositoryMock
CapesRepository * capesRepositoryMock
MojangTexturesProvider * mojangTexturesProviderMock
2021-02-26 07:15:45 +05:30
TexturesSigner * texturesSignerMock
2020-01-29 04:04:15 +05:30
Emitter * emitterMock
2020-01-02 02:12:45 +05:30
}
/ * * * * * * * * * * * * * * * * * * * *
* Setup test suite *
* * * * * * * * * * * * * * * * * * * * /
func ( suite * skinsystemTestSuite ) SetupTest ( ) {
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 )
}
2020-01-02 02:12:45 +05:30
suite . SkinsRepository = & skinsRepositoryMock { }
suite . CapesRepository = & capesRepositoryMock { }
suite . MojangTexturesProvider = & mojangTexturesProviderMock { }
2021-02-26 07:15:45 +05:30
suite . TexturesSigner = & texturesSignerMock { }
2020-01-29 04:04:15 +05:30
suite . Emitter = & emitterMock { }
2020-01-02 02:12:45 +05:30
suite . App = & Skinsystem {
2020-04-20 19:52:04 +05:30
SkinsRepo : suite . SkinsRepository ,
CapesRepo : suite . CapesRepository ,
MojangTexturesProvider : suite . MojangTexturesProvider ,
2021-02-26 07:15:45 +05:30
TexturesSigner : suite . TexturesSigner ,
2020-04-20 19:52:04 +05:30
Emitter : suite . Emitter ,
TexturesExtraParamName : "texturesParamName" ,
TexturesExtraParamValue : "texturesParamValue" ,
2020-01-02 02:12:45 +05:30
}
}
func ( suite * skinsystemTestSuite ) TearDownTest ( ) {
suite . SkinsRepository . AssertExpectations ( suite . T ( ) )
suite . CapesRepository . AssertExpectations ( suite . T ( ) )
suite . MojangTexturesProvider . AssertExpectations ( suite . T ( ) )
2021-02-26 07:15:45 +05:30
suite . TexturesSigner . AssertExpectations ( suite . T ( ) )
2020-01-29 04:04:15 +05:30
suite . Emitter . AssertExpectations ( suite . T ( ) )
2020-01-02 02:12:45 +05:30
}
func ( suite * skinsystemTestSuite ) RunSubTest ( name string , subTest func ( ) ) {
suite . SetupTest ( )
suite . Run ( name , subTest )
suite . TearDownTest ( )
}
/ * * * * * * * * * * * * *
* Run tests *
* * * * * * * * * * * * * /
func TestSkinsystem ( t * testing . T ) {
suite . Run ( t , new ( skinsystemTestSuite ) )
}
type skinsystemTestCase struct {
Name string
BeforeTest func ( suite * skinsystemTestSuite )
2021-02-26 07:15:45 +05:30
PanicErr string
2020-01-02 02:12:45 +05:30
AfterTest func ( suite * skinsystemTestSuite , response * http . Response )
}
/ * * * * * * * * * * * * * * * * * * * * * * * *
* Get skin tests cases *
* * * * * * * * * * * * * * * * * * * * * * * * /
var skinsTestsCases = [ ] * skinsystemTestCase {
{
Name : "Username exists in the local storage" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( createSkinModel ( "mock_username" , false ) , nil )
2021-02-26 07:15:45 +05:30
suite . CapesRepository . On ( "FindCapeByUsername" , "mock_username" ) . Return ( nil , nil )
2020-01-02 02:12:45 +05:30
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 301 , response . StatusCode )
suite . Equal ( "http://chrly/skin.png" , response . Header . Get ( "Location" ) )
} ,
} ,
{
Name : "Username doesn't exists on the local storage, but exists on Mojang and has textures" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
2020-04-30 00:24:40 +05:30
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Return ( createMojangResponseWithTextures ( true , false ) , nil )
2020-01-02 02:12:45 +05:30
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 301 , response . StatusCode )
suite . Equal ( "http://mojang/skin.png" , response . Header . Get ( "Location" ) )
} ,
} ,
{
2020-04-30 00:24:40 +05:30
Name : "Username doesn't exists on the local storage, but exists on Mojang and has no skin texture" ,
2020-01-02 02:12:45 +05:30
BeforeTest : func ( suite * skinsystemTestSuite ) {
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
2020-04-30 00:24:40 +05:30
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Return ( createMojangResponseWithTextures ( false , false ) , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 404 , response . StatusCode )
} ,
} ,
{
Name : "Username doesn't exists on the local storage, but exists on Mojang and has an empty properties" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Return ( createEmptyMojangResponse ( ) , nil )
2020-01-02 02:12:45 +05:30
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 404 , response . StatusCode )
} ,
} ,
{
Name : "Username doesn't exists on the local storage and doesn't exists on Mojang" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
2020-01-02 02:12:45 +05:30
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Return ( nil , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 404 , response . StatusCode )
} ,
} ,
2021-02-26 07:15:45 +05:30
{
Name : "Receive an error from the SkinsRepository" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , errors . New ( "skins repository error" ) )
} ,
PanicErr : "skins repository error" ,
} ,
2020-01-02 02:12:45 +05:30
}
func ( suite * skinsystemTestSuite ) TestSkin ( ) {
for _ , testCase := range skinsTestsCases {
suite . RunSubTest ( testCase . Name , func ( ) {
testCase . BeforeTest ( suite )
req := httptest . NewRequest ( "GET" , "http://chrly/skins/mock_username" , nil )
w := httptest . NewRecorder ( )
2021-02-26 07:15:45 +05:30
if testCase . PanicErr != "" {
suite . PanicsWithError ( testCase . PanicErr , func ( ) {
suite . App . Handler ( ) . ServeHTTP ( w , req )
} )
} else {
suite . App . Handler ( ) . ServeHTTP ( w , req )
testCase . AfterTest ( suite , w . Result ( ) )
}
2020-01-02 02:12:45 +05:30
} )
}
suite . RunSubTest ( "Pass username with png extension" , func ( ) {
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( createSkinModel ( "mock_username" , false ) , nil )
2021-02-26 07:15:45 +05:30
suite . CapesRepository . On ( "FindCapeByUsername" , "mock_username" ) . Return ( nil , nil )
2020-01-02 02:12:45 +05:30
req := httptest . NewRequest ( "GET" , "http://chrly/skins/mock_username.png" , nil )
w := httptest . NewRecorder ( )
2020-04-19 05:01:09 +05:30
suite . App . Handler ( ) . ServeHTTP ( w , req )
2020-01-02 02:12:45 +05:30
resp := w . Result ( )
suite . Equal ( 301 , resp . StatusCode )
suite . Equal ( "http://chrly/skin.png" , resp . Header . Get ( "Location" ) )
} )
}
func ( suite * skinsystemTestSuite ) TestSkinGET ( ) {
for _ , testCase := range skinsTestsCases {
suite . RunSubTest ( testCase . Name , func ( ) {
testCase . BeforeTest ( suite )
req := httptest . NewRequest ( "GET" , "http://chrly/skins?name=mock_username" , nil )
w := httptest . NewRecorder ( )
2021-02-26 07:15:45 +05:30
if testCase . PanicErr != "" {
suite . PanicsWithError ( testCase . PanicErr , func ( ) {
suite . App . Handler ( ) . ServeHTTP ( w , req )
} )
} else {
suite . App . Handler ( ) . ServeHTTP ( w , req )
testCase . AfterTest ( suite , w . Result ( ) )
}
2020-01-02 02:12:45 +05:30
} )
}
suite . RunSubTest ( "Do not pass name param" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/skins" , nil )
w := httptest . NewRecorder ( )
2020-04-19 05:01:09 +05:30
suite . App . Handler ( ) . ServeHTTP ( w , req )
2020-01-02 02:12:45 +05:30
resp := w . Result ( )
suite . Equal ( 400 , resp . StatusCode )
} )
}
/ * * * * * * * * * * * * * * * * * * * * * * * *
* Get cape tests cases *
* * * * * * * * * * * * * * * * * * * * * * * * /
var capesTestsCases = [ ] * skinsystemTestCase {
{
Name : "Username exists in the local storage" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2021-02-26 07:15:45 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( createSkinModel ( "mock_username" , false ) , nil )
2020-04-21 00:48:27 +05:30
suite . CapesRepository . On ( "FindCapeByUsername" , "mock_username" ) . Return ( createCapeModel ( ) , nil )
2020-01-02 02:12:45 +05:30
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
responseData , _ := ioutil . ReadAll ( response . Body )
suite . Equal ( createCape ( ) , responseData )
suite . Equal ( "image/png" , response . Header . Get ( "Content-Type" ) )
} ,
} ,
{
Name : "Username doesn't exists on the local storage, but exists on Mojang and has textures" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2021-02-26 07:15:45 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
2020-04-30 00:24:40 +05:30
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Return ( createMojangResponseWithTextures ( true , true ) , nil )
2020-01-02 02:12:45 +05:30
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 301 , response . StatusCode )
suite . Equal ( "http://mojang/cape.png" , response . Header . Get ( "Location" ) )
} ,
} ,
{
2020-04-30 00:24:40 +05:30
Name : "Username doesn't exists on the local storage, but exists on Mojang and has no cape texture" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2021-02-26 07:15:45 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
2020-04-30 00:24:40 +05:30
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Return ( createMojangResponseWithTextures ( false , false ) , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 404 , response . StatusCode )
} ,
} ,
{
Name : "Username doesn't exists on the local storage, but exists on Mojang and has an empty properties" ,
2020-01-02 02:12:45 +05:30
BeforeTest : func ( suite * skinsystemTestSuite ) {
2021-02-26 07:15:45 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
2020-04-30 00:24:40 +05:30
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Return ( createEmptyMojangResponse ( ) , nil )
2020-01-02 02:12:45 +05:30
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 404 , response . StatusCode )
} ,
} ,
{
Name : "Username doesn't exists on the local storage and doesn't exists on Mojang" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2021-02-26 07:15:45 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
2020-01-02 02:12:45 +05:30
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Return ( nil , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 404 , response . StatusCode )
} ,
} ,
2021-02-26 07:15:45 +05:30
{
Name : "Receive an error from the SkinsRepository" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , errors . New ( "skins repository error" ) )
} ,
PanicErr : "skins repository error" ,
} ,
2020-01-02 02:12:45 +05:30
}
func ( suite * skinsystemTestSuite ) TestCape ( ) {
for _ , testCase := range capesTestsCases {
suite . RunSubTest ( testCase . Name , func ( ) {
testCase . BeforeTest ( suite )
req := httptest . NewRequest ( "GET" , "http://chrly/cloaks/mock_username" , nil )
w := httptest . NewRecorder ( )
2021-02-26 07:15:45 +05:30
if testCase . PanicErr != "" {
suite . PanicsWithError ( testCase . PanicErr , func ( ) {
suite . App . Handler ( ) . ServeHTTP ( w , req )
} )
} else {
suite . App . Handler ( ) . ServeHTTP ( w , req )
testCase . AfterTest ( suite , w . Result ( ) )
}
2020-01-02 02:12:45 +05:30
} )
}
suite . RunSubTest ( "Pass username with png extension" , func ( ) {
2021-02-26 07:15:45 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( createSkinModel ( "mock_username" , false ) , nil )
2020-04-21 00:48:27 +05:30
suite . CapesRepository . On ( "FindCapeByUsername" , "mock_username" ) . Return ( createCapeModel ( ) , nil )
2020-01-02 02:12:45 +05:30
req := httptest . NewRequest ( "GET" , "http://chrly/cloaks/mock_username.png" , nil )
w := httptest . NewRecorder ( )
2020-04-19 05:01:09 +05:30
suite . App . Handler ( ) . ServeHTTP ( w , req )
2020-01-02 02:12:45 +05:30
resp := w . Result ( )
suite . Equal ( 200 , resp . StatusCode )
responseData , _ := ioutil . ReadAll ( resp . Body )
suite . Equal ( createCape ( ) , responseData )
suite . Equal ( "image/png" , resp . Header . Get ( "Content-Type" ) )
} )
}
func ( suite * skinsystemTestSuite ) TestCapeGET ( ) {
for _ , testCase := range capesTestsCases {
suite . RunSubTest ( testCase . Name , func ( ) {
testCase . BeforeTest ( suite )
req := httptest . NewRequest ( "GET" , "http://chrly/cloaks?name=mock_username" , nil )
w := httptest . NewRecorder ( )
2021-02-26 07:15:45 +05:30
if testCase . PanicErr != "" {
suite . PanicsWithError ( testCase . PanicErr , func ( ) {
suite . App . Handler ( ) . ServeHTTP ( w , req )
} )
} else {
suite . App . Handler ( ) . ServeHTTP ( w , req )
testCase . AfterTest ( suite , w . Result ( ) )
}
2020-01-02 02:12:45 +05:30
} )
}
suite . RunSubTest ( "Do not pass name param" , func ( ) {
req := httptest . NewRequest ( "GET" , "http://chrly/cloaks" , nil )
w := httptest . NewRecorder ( )
2020-04-19 05:01:09 +05:30
suite . App . Handler ( ) . ServeHTTP ( w , req )
2020-01-02 02:12:45 +05:30
resp := w . Result ( )
suite . Equal ( 400 , resp . StatusCode )
} )
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Get textures tests cases *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * /
var texturesTestsCases = [ ] * skinsystemTestCase {
{
Name : "Username exists and has skin, no cape" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( createSkinModel ( "mock_username" , false ) , nil )
suite . CapesRepository . On ( "FindCapeByUsername" , "mock_username" ) . Return ( nil , nil )
2020-01-02 02:12:45 +05:30
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/json" , response . Header . Get ( "Content-Type" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . JSONEq ( ` {
"SKIN" : {
"url" : "http://chrly/skin.png"
}
} ` , string ( body ) )
} ,
} ,
{
Name : "Username exists and has slim skin, no cape" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( createSkinModel ( "mock_username" , true ) , nil )
suite . CapesRepository . On ( "FindCapeByUsername" , "mock_username" ) . Return ( nil , nil )
2020-01-02 02:12:45 +05:30
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/json" , response . Header . Get ( "Content-Type" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . JSONEq ( ` {
"SKIN" : {
"url" : "http://chrly/skin.png" ,
"metadata" : {
"model" : "slim"
}
}
} ` , string ( body ) )
} ,
} ,
2021-02-26 07:15:45 +05:30
// There is no case when the user has cape, but has no skin.
// In v5 we will rework textures repositories to be more generic about source of textures,
// but right now it's not possible to return profile entity with a cape only.
2020-01-02 02:12:45 +05:30
{
Name : "Username exists and has both skin and cape" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( createSkinModel ( "mock_username" , false ) , nil )
suite . CapesRepository . On ( "FindCapeByUsername" , "mock_username" ) . Return ( createCapeModel ( ) , nil )
2020-01-02 02:12:45 +05:30
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/json" , response . Header . Get ( "Content-Type" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . JSONEq ( ` {
"SKIN" : {
"url" : "http://chrly/skin.png"
} ,
"CAPE" : {
"url" : "http://chrly/cloaks/mock_username"
}
} ` , string ( body ) )
} ,
} ,
{
Name : "Username not exists, but Mojang profile available" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
2020-04-30 00:24:40 +05:30
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Once ( ) . Return ( createMojangResponseWithTextures ( true , true ) , nil )
2020-01-02 02:12:45 +05:30
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/json" , response . Header . Get ( "Content-Type" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . JSONEq ( ` {
"SKIN" : {
"url" : "http://mojang/skin.png"
} ,
"CAPE" : {
"url" : "http://mojang/cape.png"
}
} ` , string ( body ) )
} ,
} ,
2020-04-20 22:28:31 +05:30
{
2020-04-30 00:24:40 +05:30
Name : "Username not exists, but Mojang profile available, but there is an empty skin and cape textures" ,
2020-04-20 22:28:31 +05:30
BeforeTest : func ( suite * skinsystemTestSuite ) {
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
2020-04-30 00:24:40 +05:30
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Once ( ) . Return ( createMojangResponseWithTextures ( false , false ) , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 204 , response . StatusCode )
} ,
} ,
{
Name : "Username not exists, but Mojang profile available, but there is an empty properties" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Once ( ) . Return ( createEmptyMojangResponse ( ) , nil )
2020-04-20 22:28:31 +05:30
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 204 , response . StatusCode )
} ,
} ,
2020-01-02 02:12:45 +05:30
{
Name : "Username not exists and Mojang profile unavailable" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
2020-01-02 02:12:45 +05:30
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Once ( ) . Return ( nil , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 204 , response . StatusCode )
2020-04-20 22:28:31 +05:30
body , _ := ioutil . ReadAll ( response . Body )
suite . Equal ( "" , string ( body ) )
2020-01-02 02:12:45 +05:30
} ,
} ,
2021-02-26 07:15:45 +05:30
{
Name : "Receive an error from the SkinsRepository" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , errors . New ( "skins repository error" ) )
} ,
PanicErr : "skins repository error" ,
} ,
2020-01-02 02:12:45 +05:30
}
func ( suite * skinsystemTestSuite ) TestTextures ( ) {
for _ , testCase := range texturesTestsCases {
suite . RunSubTest ( testCase . Name , func ( ) {
testCase . BeforeTest ( suite )
req := httptest . NewRequest ( "GET" , "http://chrly/textures/mock_username" , nil )
w := httptest . NewRecorder ( )
2021-02-26 07:15:45 +05:30
if testCase . PanicErr != "" {
suite . PanicsWithError ( testCase . PanicErr , func ( ) {
suite . App . Handler ( ) . ServeHTTP ( w , req )
} )
} else {
suite . App . Handler ( ) . ServeHTTP ( w , req )
testCase . AfterTest ( suite , w . Result ( ) )
}
2020-01-02 02:12:45 +05:30
} )
}
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Get signed textures tests cases *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
type signedTexturesTestCase struct {
Name string
AllowProxy bool
BeforeTest func ( suite * skinsystemTestSuite )
2021-02-26 07:15:45 +05:30
PanicErr string
2020-01-02 02:12:45 +05:30
AfterTest func ( suite * skinsystemTestSuite , response * http . Response )
}
var signedTexturesTestsCases = [ ] * signedTexturesTestCase {
{
Name : "Username exists" ,
AllowProxy : false ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( createSkinModel ( "mock_username" , true ) , nil )
2021-02-26 07:15:45 +05:30
suite . CapesRepository . On ( "FindCapeByUsername" , "mock_username" ) . Return ( nil , nil )
2020-01-02 02:12:45 +05:30
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/json" , response . Header . Get ( "Content-Type" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . JSONEq ( ` {
"id" : "0f657aa8bfbe415db7005750090d3af3" ,
"name" : "mock_username" ,
"properties" : [
{
"name" : "textures" ,
"signature" : "mocked signature" ,
"value" : "mocked textures base64"
} ,
{
2020-04-20 19:52:04 +05:30
"name" : "texturesParamName" ,
"value" : "texturesParamValue"
2020-01-02 02:12:45 +05:30
}
]
} ` , string ( body ) )
} ,
} ,
{
Name : "Username not exists" ,
AllowProxy : false ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
2020-01-02 02:12:45 +05:30
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 204 , response . StatusCode )
body , _ := ioutil . ReadAll ( response . Body )
suite . Equal ( "" , string ( body ) )
} ,
} ,
{
Name : "Username exists, but has no signed textures" ,
AllowProxy : false ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
skinModel := createSkinModel ( "mock_username" , true )
skinModel . MojangTextures = ""
skinModel . MojangSignature = ""
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( skinModel , nil )
2021-02-26 07:15:45 +05:30
suite . CapesRepository . On ( "FindCapeByUsername" , "mock_username" ) . Return ( nil , nil )
2020-01-02 02:12:45 +05:30
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 204 , response . StatusCode )
body , _ := ioutil . ReadAll ( response . Body )
suite . Equal ( "" , string ( body ) )
} ,
} ,
{
Name : "Username not exists, but Mojang profile is available and proxying is enabled" ,
AllowProxy : true ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
2020-04-30 00:24:40 +05:30
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Return ( createMojangResponseWithTextures ( true , false ) , nil )
2020-01-02 02:12:45 +05:30
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/json" , response . Header . Get ( "Content-Type" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . JSONEq ( ` {
2021-02-26 07:15:45 +05:30
"id" : "292a1db7353d476ca99cab8f57mojang" ,
2020-01-02 02:12:45 +05:30
"name" : "mock_username" ,
"properties" : [
{
"name" : "textures" ,
2021-02-26 07:15:45 +05:30
"value" : "eyJ0aW1lc3RhbXAiOjE1NTYzOTg1NzIwMDAsInByb2ZpbGVJZCI6IjI5MmExZGI3MzUzZDQ3NmNhOTljYWI4ZjU3bW9qYW5nIiwicHJvZmlsZU5hbWUiOiJtb2NrX3VzZXJuYW1lIiwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vbW9qYW5nL3NraW4ucG5nIn19fQ==" ,
"signature" : "mojang signature"
2020-01-02 02:12:45 +05:30
} ,
{
2020-04-20 19:52:04 +05:30
"name" : "texturesParamName" ,
"value" : "texturesParamValue"
2020-01-02 02:12:45 +05:30
}
]
} ` , string ( body ) )
} ,
} ,
{
Name : "Username not exists, Mojang profile is unavailable too and proxying is enabled" ,
AllowProxy : true ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
2020-04-21 00:48:27 +05:30
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
2020-01-02 02:12:45 +05:30
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Return ( nil , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 204 , response . StatusCode )
body , _ := ioutil . ReadAll ( response . Body )
suite . Equal ( "" , string ( body ) )
} ,
} ,
2021-02-26 07:15:45 +05:30
{
Name : "Receive an error from the SkinsRepository" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , errors . New ( "skins repository error" ) )
} ,
PanicErr : "skins repository error" ,
} ,
2020-01-02 02:12:45 +05:30
}
func ( suite * skinsystemTestSuite ) TestSignedTextures ( ) {
for _ , testCase := range signedTexturesTestsCases {
suite . RunSubTest ( testCase . Name , func ( ) {
testCase . BeforeTest ( suite )
var target string
if testCase . AllowProxy {
target = "http://chrly/textures/signed/mock_username?proxy=true"
} else {
target = "http://chrly/textures/signed/mock_username"
}
req := httptest . NewRequest ( "GET" , target , nil )
w := httptest . NewRecorder ( )
2021-02-26 07:15:45 +05:30
if testCase . PanicErr != "" {
suite . PanicsWithError ( testCase . PanicErr , func ( ) {
suite . App . Handler ( ) . ServeHTTP ( w , req )
} )
} else {
suite . App . Handler ( ) . ServeHTTP ( w , req )
testCase . AfterTest ( suite , w . Result ( ) )
}
} )
}
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Get profile tests cases *
* * * * * * * * * * * * * * * * * * * * * * * * * * * /
2020-01-02 02:12:45 +05:30
2021-02-26 07:15:45 +05:30
type profileTestCase struct {
Name string
Signed bool
BeforeTest func ( suite * skinsystemTestSuite )
PanicErr string
AfterTest func ( suite * skinsystemTestSuite , response * http . Response )
}
var profileTestsCases = [ ] * profileTestCase {
{
Name : "Username exists and has both skin and cape, don't sign" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( createSkinModel ( "mock_username" , false ) , nil )
suite . CapesRepository . On ( "FindCapeByUsername" , "mock_username" ) . Return ( createCapeModel ( ) , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/json" , response . Header . Get ( "Content-Type" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . JSONEq ( ` {
"id" : "0f657aa8bfbe415db7005750090d3af3" ,
"name" : "mock_username" ,
"properties" : [
{
"name" : "textures" ,
"value" : "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6IjBmNjU3YWE4YmZiZTQxNWRiNzAwNTc1MDA5MGQzYWYzIiwicHJvZmlsZU5hbWUiOiJtb2NrX3VzZXJuYW1lIiwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vY2hybHkvc2tpbi5wbmcifSwiQ0FQRSI6eyJ1cmwiOiJodHRwOi8vY2hybHkvY2xvYWtzL21vY2tfdXNlcm5hbWUifX19"
} ,
{
"name" : "texturesParamName" ,
"value" : "texturesParamValue"
}
]
} ` , string ( body ) )
} ,
} ,
{
Name : "Username exists and has both skin and cape" ,
Signed : true ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( createSkinModel ( "mock_username" , false ) , nil )
suite . CapesRepository . On ( "FindCapeByUsername" , "mock_username" ) . Return ( createCapeModel ( ) , nil )
suite . TexturesSigner . On ( "SignTextures" , "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6IjBmNjU3YWE4YmZiZTQxNWRiNzAwNTc1MDA5MGQzYWYzIiwicHJvZmlsZU5hbWUiOiJtb2NrX3VzZXJuYW1lIiwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vY2hybHkvc2tpbi5wbmcifSwiQ0FQRSI6eyJ1cmwiOiJodHRwOi8vY2hybHkvY2xvYWtzL21vY2tfdXNlcm5hbWUifX19" ) . Return ( "textures signature" , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/json" , response . Header . Get ( "Content-Type" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . JSONEq ( ` {
"id" : "0f657aa8bfbe415db7005750090d3af3" ,
"name" : "mock_username" ,
"properties" : [
{
"name" : "textures" ,
"signature" : "textures signature" ,
"value" : "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6IjBmNjU3YWE4YmZiZTQxNWRiNzAwNTc1MDA5MGQzYWYzIiwicHJvZmlsZU5hbWUiOiJtb2NrX3VzZXJuYW1lIiwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vY2hybHkvc2tpbi5wbmcifSwiQ0FQRSI6eyJ1cmwiOiJodHRwOi8vY2hybHkvY2xvYWtzL21vY2tfdXNlcm5hbWUifX19"
} ,
{
"name" : "texturesParamName" ,
"value" : "texturesParamValue"
}
]
} ` , string ( body ) )
} ,
} ,
{
Name : "Username exists and has skin, no cape" ,
Signed : true ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( createSkinModel ( "mock_username" , false ) , nil )
suite . CapesRepository . On ( "FindCapeByUsername" , "mock_username" ) . Return ( nil , nil )
suite . TexturesSigner . On ( "SignTextures" , mock . Anything ) . Return ( "textures signature" , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/json" , response . Header . Get ( "Content-Type" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . JSONEq ( ` {
"id" : "0f657aa8bfbe415db7005750090d3af3" ,
"name" : "mock_username" ,
"properties" : [
{
"name" : "textures" ,
"signature" : "textures signature" ,
"value" : "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6IjBmNjU3YWE4YmZiZTQxNWRiNzAwNTc1MDA5MGQzYWYzIiwicHJvZmlsZU5hbWUiOiJtb2NrX3VzZXJuYW1lIiwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vY2hybHkvc2tpbi5wbmcifX19"
} ,
{
"name" : "texturesParamName" ,
"value" : "texturesParamValue"
}
]
} ` , string ( body ) )
} ,
} ,
{
Name : "Username exists and has slim skin, no cape" ,
Signed : true ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( createSkinModel ( "mock_username" , true ) , nil )
suite . CapesRepository . On ( "FindCapeByUsername" , "mock_username" ) . Return ( nil , nil )
suite . TexturesSigner . On ( "SignTextures" , mock . Anything ) . Return ( "textures signature" , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/json" , response . Header . Get ( "Content-Type" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . JSONEq ( ` {
"id" : "0f657aa8bfbe415db7005750090d3af3" ,
"name" : "mock_username" ,
"properties" : [
{
"name" : "textures" ,
"signature" : "textures signature" ,
"value" : "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6IjBmNjU3YWE4YmZiZTQxNWRiNzAwNTc1MDA5MGQzYWYzIiwicHJvZmlsZU5hbWUiOiJtb2NrX3VzZXJuYW1lIiwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vY2hybHkvc2tpbi5wbmciLCJtZXRhZGF0YSI6eyJtb2RlbCI6InNsaW0ifX19fQ=="
} ,
{
"name" : "texturesParamName" ,
"value" : "texturesParamValue"
}
]
} ` , string ( body ) )
} ,
} ,
{
Name : "Username exists, but has no skin and Mojang profile with textures available" ,
Signed : true ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
skin := createSkinModel ( "mock_username" , false )
skin . SkinId = 0
skin . Url = ""
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( skin , nil )
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Once ( ) . Return ( createMojangResponseWithTextures ( true , true ) , nil )
suite . TexturesSigner . On ( "SignTextures" , mock . Anything ) . Return ( "chrly signature" , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/json" , response . Header . Get ( "Content-Type" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . JSONEq ( ` {
"id" : "0f657aa8bfbe415db7005750090d3af3" ,
"name" : "mock_username" ,
"properties" : [
{
"name" : "textures" ,
"signature" : "chrly signature" ,
"value" : "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6IjBmNjU3YWE4YmZiZTQxNWRiNzAwNTc1MDA5MGQzYWYzIiwicHJvZmlsZU5hbWUiOiJtb2NrX3VzZXJuYW1lIiwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vbW9qYW5nL3NraW4ucG5nIn0sIkNBUEUiOnsidXJsIjoiaHR0cDovL21vamFuZy9jYXBlLnBuZyJ9fX0="
} ,
{
"name" : "texturesParamName" ,
"value" : "texturesParamValue"
}
]
} ` , string ( body ) )
} ,
} ,
{
Name : "Username exists, but has no skin and Mojang textures proxy returned an error" ,
Signed : true ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
skin := createSkinModel ( "mock_username" , false )
skin . SkinId = 0
skin . Url = ""
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( skin , nil )
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Once ( ) . Return ( nil , errors . New ( "shit happened" ) )
suite . TexturesSigner . On ( "SignTextures" , mock . Anything ) . Return ( "chrly signature" , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/json" , response . Header . Get ( "Content-Type" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . JSONEq ( ` {
"id" : "0f657aa8bfbe415db7005750090d3af3" ,
"name" : "mock_username" ,
"properties" : [
{
"name" : "textures" ,
"signature" : "chrly signature" ,
"value" : "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6IjBmNjU3YWE4YmZiZTQxNWRiNzAwNTc1MDA5MGQzYWYzIiwicHJvZmlsZU5hbWUiOiJtb2NrX3VzZXJuYW1lIiwidGV4dHVyZXMiOnt9fQ=="
} ,
{
"name" : "texturesParamName" ,
"value" : "texturesParamValue"
}
]
} ` , string ( body ) )
} ,
} ,
{
Name : "Username not exists, but Mojang profile with textures available" ,
Signed : true ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Once ( ) . Return ( createMojangResponseWithTextures ( true , true ) , nil )
suite . TexturesSigner . On ( "SignTextures" , mock . Anything ) . Return ( "chrly signature" , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/json" , response . Header . Get ( "Content-Type" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . JSONEq ( ` {
"id" : "292a1db7353d476ca99cab8f57mojang" ,
"name" : "mock_username" ,
"properties" : [
{
"name" : "textures" ,
"signature" : "chrly signature" ,
"value" : "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6IjI5MmExZGI3MzUzZDQ3NmNhOTljYWI4ZjU3bW9qYW5nIiwicHJvZmlsZU5hbWUiOiJtb2NrX3VzZXJuYW1lIiwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vbW9qYW5nL3NraW4ucG5nIn0sIkNBUEUiOnsidXJsIjoiaHR0cDovL21vamFuZy9jYXBlLnBuZyJ9fX0="
} ,
{
"name" : "texturesParamName" ,
"value" : "texturesParamValue"
}
]
} ` , string ( body ) )
} ,
} ,
{
Name : "Username not exists, but Mojang profile available, but there is an empty skin and cape textures" ,
Signed : true ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Once ( ) . Return ( createMojangResponseWithTextures ( false , false ) , nil )
suite . TexturesSigner . On ( "SignTextures" , mock . Anything ) . Return ( "chrly signature" , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/json" , response . Header . Get ( "Content-Type" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . JSONEq ( ` {
"id" : "292a1db7353d476ca99cab8f57mojang" ,
"name" : "mock_username" ,
"properties" : [
{
"name" : "textures" ,
"signature" : "chrly signature" ,
"value" : "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6IjI5MmExZGI3MzUzZDQ3NmNhOTljYWI4ZjU3bW9qYW5nIiwicHJvZmlsZU5hbWUiOiJtb2NrX3VzZXJuYW1lIiwidGV4dHVyZXMiOnt9fQ=="
} ,
{
"name" : "texturesParamName" ,
"value" : "texturesParamValue"
}
]
} ` , string ( body ) )
} ,
} ,
{
Name : "Username not exists, but Mojang profile available, but there is an empty properties" ,
Signed : true ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Once ( ) . Return ( createEmptyMojangResponse ( ) , nil )
suite . TexturesSigner . On ( "SignTextures" , mock . Anything ) . Return ( "chrly signature" , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 200 , response . StatusCode )
suite . Equal ( "application/json" , response . Header . Get ( "Content-Type" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . JSONEq ( ` {
"id" : "292a1db7353d476ca99cab8f57mojang" ,
"name" : "mock_username" ,
"properties" : [
{
"name" : "textures" ,
"signature" : "chrly signature" ,
"value" : "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6IjI5MmExZGI3MzUzZDQ3NmNhOTljYWI4ZjU3bW9qYW5nIiwicHJvZmlsZU5hbWUiOiJtb2NrX3VzZXJuYW1lIiwidGV4dHVyZXMiOnt9fQ=="
} ,
{
"name" : "texturesParamName" ,
"value" : "texturesParamValue"
}
]
} ` , string ( body ) )
} ,
} ,
{
Name : "Username not exists and Mojang profile unavailable" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Once ( ) . Return ( nil , nil )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
suite . Equal ( 204 , response . StatusCode )
body , _ := ioutil . ReadAll ( response . Body )
suite . Equal ( "" , string ( body ) )
} ,
} ,
{
Name : "Username not exists and Mojang textures proxy returned an error" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , nil )
suite . MojangTexturesProvider . On ( "GetForUsername" , "mock_username" ) . Once ( ) . Return ( nil , errors . New ( "mojang textures provider error" ) )
} ,
PanicErr : "mojang textures provider error" ,
} ,
{
Name : "Receive an error from the SkinsRepository" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( nil , errors . New ( "skins repository error" ) )
} ,
PanicErr : "skins repository error" ,
} ,
{
Name : "Receive an error from the TexturesSigner" ,
Signed : true ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . SkinsRepository . On ( "FindSkinByUsername" , "mock_username" ) . Return ( createSkinModel ( "mock_username" , false ) , nil )
suite . CapesRepository . On ( "FindCapeByUsername" , "mock_username" ) . Return ( nil , nil )
suite . TexturesSigner . On ( "SignTextures" , mock . Anything ) . Return ( "" , errors . New ( "textures signer error" ) )
} ,
PanicErr : "textures signer error" ,
} ,
}
func ( suite * skinsystemTestSuite ) TestProfile ( ) {
for _ , testCase := range profileTestsCases {
suite . RunSubTest ( testCase . Name , func ( ) {
testCase . BeforeTest ( suite )
url := "http://chrly/profile/mock_username"
if testCase . Signed {
url += "?unsigned=false"
}
req := httptest . NewRequest ( "GET" , url , nil )
w := httptest . NewRecorder ( )
if testCase . PanicErr != "" {
suite . PanicsWithError ( testCase . PanicErr , func ( ) {
suite . App . Handler ( ) . ServeHTTP ( w , req )
} )
} else {
suite . App . Handler ( ) . ServeHTTP ( w , req )
testCase . AfterTest ( suite , w . Result ( ) )
}
} )
}
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Get profile tests cases *
* * * * * * * * * * * * * * * * * * * * * * * * * * * /
2021-03-03 18:03:56 +05:30
type signingKeyTestCase struct {
Name string
KeyFormat string
BeforeTest func ( suite * skinsystemTestSuite )
PanicErr string
AfterTest func ( suite * skinsystemTestSuite , response * http . Response )
}
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" ,
2021-02-26 07:15:45 +05:30
BeforeTest : func ( suite * skinsystemTestSuite ) {
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 )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
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" ) )
2021-02-26 07:15:45 +05:30
body , _ := ioutil . ReadAll ( response . Body )
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" ,
BeforeTest : func ( suite * skinsystemTestSuite ) {
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 )
} ,
AfterTest : func ( suite * skinsystemTestSuite , response * http . Response ) {
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" ) )
body , _ := ioutil . ReadAll ( response . Body )
suite . Equal ( "-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnm\nUDlzHBQH3DpYef5WCO32TDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQ==\n-----END PUBLIC KEY-----\n" , string ( body ) )
} ,
} ,
{
Name : "Error while obtaining public key" ,
KeyFormat : "DER" ,
2021-02-26 07:15:45 +05:30
BeforeTest : func ( suite * skinsystemTestSuite ) {
suite . TexturesSigner . On ( "GetPublicKey" ) . Return ( nil , errors . New ( "textures signer error" ) )
} ,
PanicErr : "textures signer error" ,
} ,
}
2021-02-27 07:07:59 +05:30
func ( suite * skinsystemTestSuite ) TestSignatureVerificationKey ( ) {
2021-02-26 07:15:45 +05:30
for _ , testCase := range signingKeyTestsCases {
suite . RunSubTest ( testCase . Name , func ( ) {
testCase . BeforeTest ( suite )
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 != "" {
suite . PanicsWithError ( testCase . PanicErr , func ( ) {
suite . App . Handler ( ) . ServeHTTP ( w , req )
} )
} else {
suite . App . Handler ( ) . ServeHTTP ( w , req )
testCase . AfterTest ( suite , w . Result ( ) )
}
2020-01-02 02:12:45 +05:30
} )
}
}
/ * * * * * * * * * * * * * * * *
* Custom tests *
* * * * * * * * * * * * * * * * /
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" )
}
/ * * * * * * * * * * * * *
* Utilities *
* * * * * * * * * * * * * /
func createSkinModel ( username string , isSlim bool ) * model . Skin {
return & model . Skin {
UserId : 1 ,
Username : username ,
Uuid : "0f657aa8-bfbe-415d-b700-5750090d3af3" , // Use non nil UUID to pass validation in api tests
SkinId : 1 ,
Url : "http://chrly/skin.png" ,
MojangTextures : "mocked textures base64" ,
MojangSignature : "mocked signature" ,
IsSlim : isSlim ,
}
}
func createCape ( ) [ ] byte {
img := image . NewAlpha ( image . Rect ( 0 , 0 , 64 , 32 ) )
writer := & bytes . Buffer { }
_ = png . Encode ( writer , img )
pngBytes , _ := ioutil . ReadAll ( writer )
return pngBytes
}
func createCapeModel ( ) * model . Cape {
return & model . Cape { File : bytes . NewReader ( createCape ( ) ) }
}
2020-04-30 00:24:40 +05:30
func createEmptyMojangResponse ( ) * mojang . SignedTexturesResponse {
return & mojang . SignedTexturesResponse {
2021-02-26 07:15:45 +05:30
Id : "292a1db7353d476ca99cab8f57mojang" ,
2020-04-30 00:24:40 +05:30
Name : "mock_username" ,
Props : [ ] * mojang . Property { } ,
}
}
func createMojangResponseWithTextures ( includeSkin bool , includeCape bool ) * mojang . SignedTexturesResponse {
2020-01-02 02:12:45 +05:30
timeZone , _ := time . LoadLocation ( "Europe/Minsk" )
textures := & mojang . TexturesProp {
2021-02-26 07:15:45 +05:30
Timestamp : time . Date ( 2019 , 4 , 27 , 23 , 56 , 12 , 0 , timeZone ) . UnixNano ( ) / int64 ( time . Millisecond ) ,
ProfileID : "292a1db7353d476ca99cab8f57mojang" ,
2020-01-02 02:12:45 +05:30
ProfileName : "mock_username" ,
Textures : & mojang . TexturesResponse { } ,
}
if includeSkin {
textures . Textures . Skin = & mojang . SkinTexturesResponse {
Url : "http://mojang/skin.png" ,
}
}
if includeCape {
textures . Textures . Cape = & mojang . CapeTexturesResponse {
Url : "http://mojang/cape.png" ,
}
}
2020-04-30 00:24:40 +05:30
response := createEmptyMojangResponse ( )
response . Props = append ( response . Props , & mojang . Property {
2021-02-26 07:15:45 +05:30
Name : "textures" ,
Value : mojang . EncodeTextures ( textures ) ,
Signature : "mojang signature" ,
2020-04-30 00:24:40 +05:30
} )
2020-01-02 02:12:45 +05:30
return response
}