Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,25 +67,178 @@ describe('Update profile routes', () => {
it('correctly defines route.', () => {
const bodySchema = (routeConfig.validate as any).body as ObjectType;
expect(() => bodySchema.validate(0)).toThrowErrorMatchingInlineSnapshot(
`"expected value of type [object] but got [number]"`
`"expected a plain object value, but found [number] instead."`
);
expect(() => bodySchema.validate('avatar')).toThrowErrorMatchingInlineSnapshot(
`"could not parse record value from json input"`
`"could not parse object value from json input"`
);
expect(() => bodySchema.validate(true)).toThrowErrorMatchingInlineSnapshot(
`"expected value of type [object] but got [boolean]"`
);
expect(() => bodySchema.validate(null)).toThrowErrorMatchingInlineSnapshot(
`"expected value of type [object] but got [null]"`
);
expect(() => bodySchema.validate(undefined)).toThrowErrorMatchingInlineSnapshot(
`"expected value of type [object] but got [undefined]"`
`"expected a plain object value, but found [boolean] instead."`
);

expect(bodySchema.validate({})).toEqual({});
expect(
bodySchema.validate({ title: 'some-title', content: { deepProperty: { type: 'basic' } } })
).toEqual({ title: 'some-title', content: { deepProperty: { type: 'basic' } } });
bodySchema.validate({
avatar: { initials: 'some-initials', color: 'some-color', imageUrl: 'some-image-url' },
userSettings: { darkMode: 'dark', contrastMode: 'high' },
})
).toEqual({
avatar: { initials: 'some-initials', color: 'some-color', imageUrl: 'some-image-url' },
userSettings: { darkMode: 'dark', contrastMode: 'high' },
});
});

it('rejects invalid darkMode enum values.', () => {
const bodySchema = (routeConfig.validate as any).body as ObjectType;

// Valid values should pass
expect(
bodySchema.validate({
userSettings: { darkMode: 'system' },
})
).toEqual({ userSettings: { darkMode: 'system' } });

expect(
bodySchema.validate({
userSettings: { darkMode: 'dark' },
})
).toEqual({ userSettings: { darkMode: 'dark' } });

expect(
bodySchema.validate({
userSettings: { darkMode: 'light' },
})
).toEqual({ userSettings: { darkMode: 'light' } });

expect(
bodySchema.validate({
userSettings: { darkMode: 'space_default' },
})
).toEqual({ userSettings: { darkMode: 'space_default' } });

// Invalid values should fail
expect(() =>
bodySchema.validate({
userSettings: { darkMode: 'invalid' },
})
).toThrow();

expect(() =>
bodySchema.validate({
userSettings: { darkMode: 'INVALID' },
})
).toThrow();

expect(() =>
bodySchema.validate({
userSettings: { darkMode: 123 },
})
).toThrow();
});

it('rejects invalid contrastMode enum values.', () => {
const bodySchema = (routeConfig.validate as any).body as ObjectType;

// Valid values should pass
expect(
bodySchema.validate({
userSettings: { contrastMode: 'system' },
})
).toEqual({ userSettings: { contrastMode: 'system' } });

expect(
bodySchema.validate({
userSettings: { contrastMode: 'standard' },
})
).toEqual({ userSettings: { contrastMode: 'standard' } });

expect(
bodySchema.validate({
userSettings: { contrastMode: 'high' },
})
).toEqual({ userSettings: { contrastMode: 'high' } });

// Invalid values should fail
expect(() =>
bodySchema.validate({
userSettings: { contrastMode: 'invalid' },
})
).toThrow();

expect(() =>
bodySchema.validate({
userSettings: { contrastMode: 'INVALID' },
})
).toThrow();

expect(() =>
bodySchema.validate({
userSettings: { contrastMode: 123 },
})
).toThrow();
});

it('rejects avatar initials exceeding max length.', () => {
const bodySchema = (routeConfig.validate as any).body as ObjectType;
const MAX_STRING_FIELD_LENGTH = 1024;

// Valid length should pass
const validInitials = 'a'.repeat(MAX_STRING_FIELD_LENGTH);
expect(
bodySchema.validate({
avatar: { initials: validInitials },
})
).toEqual({ avatar: { color: null, imageUrl: null, initials: validInitials } });

const invalidInitials = 'a'.repeat(MAX_STRING_FIELD_LENGTH + 1);
expect(() =>
bodySchema.validate({
avatar: { initials: invalidInitials },
})
).toThrowErrorMatchingInlineSnapshot(`
"[avatar.initials]: types that failed validation:
- [avatar.initials.0]: value has length [1025] but it must have a maximum length of [1024].
- [avatar.initials.1]: expected value to equal [null]"
`);
});

it('rejects avatar color exceeding max length.', () => {
const bodySchema = (routeConfig.validate as any).body as ObjectType;
const MAX_STRING_FIELD_LENGTH = 1024;

const validColor = 'a'.repeat(MAX_STRING_FIELD_LENGTH);
expect(
bodySchema.validate({
avatar: { color: validColor },
})
).toEqual({ avatar: { color: validColor, imageUrl: null, initials: null } });

const invalidColor = 'a'.repeat(MAX_STRING_FIELD_LENGTH + 1);
expect(() =>
bodySchema.validate({
avatar: { color: invalidColor },
})
).toThrowErrorMatchingInlineSnapshot(`
"[avatar.color]: types that failed validation:
- [avatar.color.0]: value has length [1025] but it must have a maximum length of [1024].
- [avatar.color.1]: expected value to equal [null]"
`);
});

it('allows null values for avatar initials and color.', () => {
const bodySchema = (routeConfig.validate as any).body as ObjectType;

expect(
bodySchema.validate({
avatar: { initials: null, color: null },
})
).toEqual({ avatar: { imageUrl: null, initials: null, color: null } });
});

it('validates body size limit is configured correctly.', () => {
const MAX_USER_PROFILE_DATA_SIZE_BYTES = 1000 * 1024;

expect(routeConfig.options?.body?.maxBytes).toBe(MAX_USER_PROFILE_DATA_SIZE_BYTES);
});

it('fails if session is not found.', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,36 @@ const ALLOWED_KEYS_UPDATE_CLOUD = [
'solutionNavigationTour:completed', // TODO: remove with https://github.com/elastic/kibana/issues/239313
];

const MAX_STRING_FIELD_LENGTH = 1024;

const MAX_USER_PROFILE_DATA_SIZE_BYTES = 1000 * 1024;

const userProfileUpdateSchema = schema.object({
avatar: schema.maybe(
schema.object({
initials: schema.nullable(schema.string({ maxLength: MAX_STRING_FIELD_LENGTH })),
color: schema.nullable(schema.string({ maxLength: MAX_STRING_FIELD_LENGTH })),
imageUrl: schema.nullable(schema.string()),
})
),
userSettings: schema.maybe(
schema.object({
darkMode: schema.maybe(
schema.oneOf([
schema.literal('system'),
schema.literal('dark'),
schema.literal('light'),
schema.literal('space_default'),
])
),
contrastMode: schema.maybe(
schema.oneOf([schema.literal('system'), schema.literal('standard'), schema.literal('high')])
),
'solutionNavigationTour:completed': schema.maybe(schema.boolean()),
})
),
});

export function defineUpdateUserProfileDataRoute({
router,
getSession,
Expand All @@ -39,7 +69,12 @@ export function defineUpdateUserProfileDataRoute({
},
},
validate: {
body: schema.recordOf(schema.string(), schema.any()),
body: userProfileUpdateSchema,
},
options: {
body: {
maxBytes: MAX_USER_PROFILE_DATA_SIZE_BYTES,
},
},
},
createLicensedRouteHandler(async (context, request, response) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,11 +470,10 @@ export default ({ getService }: FtrProviderContext): void => {
supertest,
req: {
// @ts-expect-error: types are not correct
initials: 4,
// @ts-expect-error: types are not correct
color: true,
initials: null,
// @ts-expect-error: types are not correct
imageUrl: [],
color: null,
imageUrl: null,
},
headers: superUserHeaders,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,13 @@ export default function ({ getService }: FtrProviderContext) {
.post('/internal/security/user_profile/_data')
.set('kbn-xsrf', 'xxx')
.set('Cookie', usersSessions.get(`user_${userPrefix}`)!.cookie.cookieString())
.send({ some: `data-${userPrefix}` })
.send({
avatar: {
initials: `some-initials-${userPrefix}`,
color: `some-color-${userPrefix}`,
},
userSettings: { darkMode: `dark`, contrastMode: `high` },
})
.expect(200)
)
);
Expand Down Expand Up @@ -145,7 +151,7 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'xxx')
.send({
uids: [usersSessions.get('user_one')!.uid, usersSessions.get('user_two')!.uid],
dataPath: 'some',
dataPath: 'avatar',
})
.expect(200);
expect(profiles.body).to.have.length(2);
Expand All @@ -155,7 +161,11 @@ export default function ({ getService }: FtrProviderContext) {
Array [
Object {
"data": Object {
"some": "data-one",
"avatar": Object {
"color": "some-color-one",
"imageUrl": null,
"initials": "some-initials-one",
},
},
"user": Object {
"email": "one@elastic.co",
Expand All @@ -165,7 +175,11 @@ export default function ({ getService }: FtrProviderContext) {
},
Object {
"data": Object {
"some": "data-two",
"avatar": Object {
"color": "some-color-two",
"imageUrl": null,
"initials": "some-initials-two",
},
},
"user": Object {
"email": "two@elastic.co",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ export default function ({ getService }: FtrProviderContext) {
.post('/internal/security/user_profile/_data')
.set('kbn-xsrf', 'xxx')
.set('Cookie', sessionCookie.cookieString())
.send({ some: 'data', another: 'another-data' })
.send({
avatar: { initials: 'some-initials', color: 'some-color' },
userSettings: { darkMode: 'dark', contrastMode: 'high' },
})
.expect(200);

const { body: profileWithoutData } = await supertestWithoutAuth
Expand Down Expand Up @@ -96,8 +99,15 @@ export default function ({ getService }: FtrProviderContext) {
expectSnapshot(profileWithAllData).toMatchInline(`
Object {
"data": Object {
"another": "another-data",
"some": "data",
"avatar": Object {
"color": "some-color",
"imageUrl": null,
"initials": "some-initials",
},
"userSettings": Object {
"contrastMode": "high",
"darkMode": "dark",
},
},
"enabled": true,
"labels": Object {},
Expand All @@ -119,9 +129,7 @@ export default function ({ getService }: FtrProviderContext) {
`);
expectSnapshot(profileWithSomeData).toMatchInline(`
Object {
"data": Object {
"some": "data",
},
"data": Object {},
"enabled": true,
"labels": Object {},
"uid": "u_K1WXIRQbRoHiuJylXp842IEhAO_OdqT7SDHrJSzUIjU_0",
Expand Down
Loading