Skip to content

Commit

Permalink
feat: validate scene thumbnail (#201)
Browse files Browse the repository at this point in the history
  • Loading branch information
marianogoldman committed May 15, 2023
1 parent e6d3e4c commit ba30d03
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 66 deletions.
48 changes: 32 additions & 16 deletions src/validations/scene.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,43 @@
import { EntityType } from '@dcl/schemas'
import { DeploymentToValidate, OK, ValidateFn, validationFailed, ValidationResponse } from '../types'
import { validateAfterADR173, validateIfTypeMatches } from './validations'

function validateIfScene(validateFn: ValidateFn): ValidateFn {
return validateIfTypeMatches(EntityType.SCENE, validateFn)
}
import { ContentMapping, EntityType } from '@dcl/schemas'
import { DeploymentToValidate, OK, validationFailed, ValidationResponse } from '../types'
import { validateAfterADR173, validateAfterADR236, validateAll, validateIfTypeMatches } from './validations'

/**
* Validate that given scene deployment does not contain worldConfiguration section
* @public
*/
export const noWorldsConfigurationValidateFn = validateIfScene(
validateAfterADR173(async function validateFn(deployment: DeploymentToValidate): Promise<ValidationResponse> {
const sceneHasWorldConfiguration = deployment.entity.metadata?.worldConfiguration !== undefined
if (sceneHasWorldConfiguration) {
return validationFailed('Scenes cannot have worldConfiguration section after ADR 173.')
export const noWorldsConfigurationValidateFn = validateAfterADR173(async function validateFn(
deployment: DeploymentToValidate
): Promise<ValidationResponse> {
const sceneHasWorldConfiguration = deployment.entity.metadata?.worldConfiguration !== undefined
if (sceneHasWorldConfiguration) {
return validationFailed('Scenes cannot have worldConfiguration section after ADR 173.')
}
return OK
})

/**
* Validate that given scene deployment thumbnail is a file embedded in the deployment
* @public
*/
export const embeddedThumbnail = validateAfterADR236(async function validateFn(
deployment: DeploymentToValidate
): Promise<ValidationResponse> {
const sceneThumbnail = deployment.entity.metadata?.display.navmapThumbnail
if (sceneThumbnail) {
const isFilePresent = deployment.entity.content.some((content: ContentMapping) => content.file === sceneThumbnail)
if (!isFilePresent) {
return validationFailed(`Scene thumbnail '${sceneThumbnail}' must be a file included in the deployment.`)
}
return OK
})
)
}
return OK
})

/**
* Validate that given scene deployment is valid
* @public
*/
export const sceneValidateFn = noWorldsConfigurationValidateFn
export const sceneValidateFn = validateIfTypeMatches(
EntityType.SCENE,
validateAll(noWorldsConfigurationValidateFn, embeddedThumbnail)
)
6 changes: 6 additions & 0 deletions src/validations/timestamps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ export const ADR_158_TIMESTAMP = process.env.ADR_158_TIMESTAMP ? parseInt(proces
*/
export const ADR_173_TIMESTAMP = process.env.ADR_173_TIMESTAMP ? parseInt(process.env.ADR_173_TIMESTAMP) : 1673967600000

/**
* 1681660800000 = 2023-05-19T12:00:00Z
* @public
*/
export const ADR_236_TIMESTAMP = process.env.ADR_236_TIMESTAMP ? parseInt(process.env.ADR_236_TIMESTAMP) : 1681660800000

/**
* DCL Launch Day
* @public
Expand Down
5 changes: 5 additions & 0 deletions src/validations/validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DeploymentToValidate, OK, ValidateFn } from '../types'
import {
ADR_158_TIMESTAMP,
ADR_173_TIMESTAMP,
ADR_236_TIMESTAMP,
ADR_45_TIMESTAMP,
ADR_74_TIMESTAMP,
ADR_75_TIMESTAMP
Expand Down Expand Up @@ -53,6 +54,10 @@ export function validateAfterADR173(validate: ValidateFn): ValidateFn {
return validateIfConditionMet((deployment) => deployment.entity.timestamp >= ADR_173_TIMESTAMP, validate)
}

export function validateAfterADR236(validate: ValidateFn): ValidateFn {
return validateIfConditionMet((deployment) => deployment.entity.timestamp >= ADR_236_TIMESTAMP, validate)
}

export function validateIfTypeMatches(entityType: EntityType, validate: ValidateFn) {
return validateIfConditionMet((deployment) => deployment.entity.type === entityType, validate)
}
16 changes: 16 additions & 0 deletions test/setup/mock.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ILoggerComponent } from '@well-known-components/interfaces'
import { ISubgraphComponent, Variables } from '@well-known-components/thegraph-component'
import { ContentValidatorComponents, ExternalCalls, ValidateFn } from '../../src/types'
import sharp from 'sharp'

export type QueryGraph = <T = any>(query: string, variables?: Variables, remainingAttempts?: number) => Promise<T>

Expand Down Expand Up @@ -40,3 +41,18 @@ export function buildExternalCalls(externalCalls?: Partial<ExternalCalls>): Exte
export const createMockSubgraphComponent = (mock?: QueryGraph): ISubgraphComponent => ({
query: mock ?? (jest.fn() as jest.MockedFunction<QueryGraph>)
})

export const createImage = async (size: number, format: 'png' | 'jpg' = 'png'): Promise<Buffer> => {
let image = sharp({
create: {
width: size,
height: size,
channels: 4,
background: { r: 255, g: 0, b: 0, alpha: 0.5 }
}
})
if (format) {
image = format === 'png' ? image.png() : image.jpeg()
}
return await image.toBuffer()
}
17 changes: 1 addition & 16 deletions test/unit/validations/items/emotes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { ADR_74_TIMESTAMP } from '../../../../src/validations/timestamps'
import { buildDeployment } from '../../../setup/deployments'
import { VALID_STANDARD_EMOTE_METADATA } from '../../../setup/emotes'
import { buildEntity } from '../../../setup/entity'
import { buildComponents, buildExternalCalls } from '../../../setup/mock'
import { buildComponents, buildExternalCalls, createImage } from '../../../setup/mock'

describe('Emotes', () => {
const timestampAfterADR74 = ADR_74_TIMESTAMP + 1
Expand All @@ -24,21 +24,6 @@ describe('Emotes', () => {
const fileName = 'thumbnail.png'
const hash = 'thumbnail'

const createImage = async (size: number, format: 'png' | 'jpg' = 'png'): Promise<Buffer> => {
let image = sharp({
create: {
width: size,
height: size,
channels: 4,
background: { r: 255, g: 0, b: 0, alpha: 0.5 }
}
})
if (format) {
image = format === 'png' ? image.png() : image.jpeg()
}
return await image.toBuffer()
}

beforeAll(async () => {
validThumbnailBuffer = await createImage(1024)
invalidThumbnailBuffer = await createImage(1025)
Expand Down
17 changes: 1 addition & 16 deletions test/unit/validations/items/wearables.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { createSizeValidateFn } from '../../../../src/validations/size'
import { ADR_45_TIMESTAMP } from '../../../../src/validations/timestamps'
import { buildDeployment } from '../../../setup/deployments'
import { buildEntity } from '../../../setup/entity'
import { buildComponents, buildExternalCalls } from '../../../setup/mock'
import { buildComponents, buildExternalCalls, createImage } from '../../../setup/mock'
import { VALID_WEARABLE_METADATA } from '../../../setup/wearable'

describe('Wearables', () => {
Expand All @@ -22,21 +22,6 @@ describe('Wearables', () => {
const fileName = 'thumbnail.png'
const hash = 'thumbnail'

const createImage = async (size: number, format: 'png' | 'jpg' = 'png'): Promise<Buffer> => {
let image = sharp({
create: {
width: size,
height: size,
channels: 4,
background: { r: 255, g: 0, b: 0, alpha: 0.5 }
}
})
if (format) {
image = format === 'png' ? image.png() : image.jpeg()
}
return await image.toBuffer()
}

beforeAll(async () => {
validThumbnailBuffer = await createImage(1024)
invalidThumbnailBuffer = await createImage(1025)
Expand Down
17 changes: 1 addition & 16 deletions test/unit/validations/profiles.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { ADR_45_TIMESTAMP, ADR_74_TIMESTAMP, ADR_75_TIMESTAMP } from '../../../src/validations/timestamps'
import { buildDeployment } from '../../setup/deployments'
import { buildEntity } from '../../setup/entity'
import { buildComponents, buildExternalCalls } from '../../setup/mock'
import { buildComponents, buildExternalCalls, createImage } from '../../setup/mock'
import { validProfileMetadataWithEmotes, VALID_PROFILE_METADATA } from '../../setup/profiles'

describe('Profiles', () => {
Expand All @@ -26,21 +26,6 @@ describe('Profiles', () => {
const fileName = 'face256.png'
const hash = 'bafybeiasb5vpmaounyilfuxbd3lryvosl4yefqrfahsb2esg46q6tu6y5s'

const createImage = async (size: number, format: 'png' | 'jpg' = 'png'): Promise<Buffer> => {
let image = sharp({
create: {
width: size,
height: size,
channels: 4,
background: { r: 255, g: 0, b: 0, alpha: 0.5 }
}
})
if (format) {
image = format === 'png' ? image.png() : image.jpeg()
}
return await image.toBuffer()
}

beforeAll(async () => {
validThumbnailBuffer = await createImage(256)
invalidThumbnailBuffer = await createImage(1)
Expand Down
61 changes: 59 additions & 2 deletions test/unit/validations/scenes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { EntityType } from '@dcl/schemas'
import { ValidationResponse } from '../../../src'
import { noWorldsConfigurationValidateFn } from '../../../src/validations/scene'
import { ADR_173_TIMESTAMP } from '../../../src/validations/timestamps'
import { embeddedThumbnail, noWorldsConfigurationValidateFn } from '../../../src/validations/scene'
import { ADR_173_TIMESTAMP, ADR_236_TIMESTAMP } from '../../../src/validations/timestamps'
import { buildDeployment } from '../../setup/deployments'
import { buildEntity } from '../../setup/entity'
import { VALID_SCENE_METADATA } from '../../setup/scenes'
import { createImage } from '../../setup/mock'

describe('Scenes', () => {
const timestamp = ADR_173_TIMESTAMP + 1
Expand Down Expand Up @@ -43,4 +44,60 @@ describe('Scenes', () => {
expect(result.errors).toContain('Scenes cannot have worldConfiguration section after ADR 173.')
})
})

describe('embeddedThumbnail validation:', () => {
const timestamp = ADR_236_TIMESTAMP + 1

let thumbnailBuffer: Buffer
const content = [{ file: 'thumbnail.png', hash: 'thumbnailHash' }]
const files = new Map()

beforeAll(async () => {
thumbnailBuffer = await createImage(1024)
files.set('thumbnailHash', thumbnailBuffer)
})

it('When there is a thumbnail that references an embedded file, validation succeeds', async () => {
const entity = buildEntity({
type: EntityType.SCENE,
metadata: {
...VALID_SCENE_METADATA,
display: {
...VALID_SCENE_METADATA.display,
navmapThumbnail: 'thumbnail.png'
}
},
content,
timestamp
})
const deployment = buildDeployment({ entity, files })

const result: ValidationResponse = await embeddedThumbnail(deployment)

expect(result.ok).toBeTruthy()
})

it('When there is a thumbnail that does not reference an embedded file, validation fails with error', async () => {
const entity = buildEntity({
type: EntityType.SCENE,
metadata: {
...VALID_SCENE_METADATA,
display: {
...VALID_SCENE_METADATA.display,
navmapThumbnail: 'https://example.com/image.png' // Invalid, it must reference a file in the deployment
}
},
content,
timestamp
})
const deployment = buildDeployment({ entity, files })

const result: ValidationResponse = await embeddedThumbnail(deployment)

expect(result.ok).toBeFalsy()
expect(result.errors).toContain(
"Scene thumbnail 'https://example.com/image.png' must be a file included in the deployment."
)
})
})
})

0 comments on commit ba30d03

Please sign in to comment.