fix(ombi): resolve TV request status, user, and date display (Issue #53)
Build and Push Docker Image / build (push) Successful in 1m46s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 1m33s
CI / Security audit (push) Successful in 1m56s
CI / Swagger Validation & Coverage (push) Successful in 2m35s
CI / Tests & coverage (push) Successful in 2m51s
Build and Push Docker Image / build (push) Successful in 1m46s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 1m33s
CI / Security audit (push) Successful in 1m56s
CI / Swagger Validation & Coverage (push) Successful in 2m35s
CI / Tests & coverage (push) Successful in 2m51s
Ombi's TV API nests all request data (requestedUser, approved, available, denied, requested, requestedDate) inside childRequests[] sub-objects. The application previously only inspected top-level properties, causing TV shows to consistently display 'unknown' status, 'unknown' user, and no request date. Changes: - OmbiRetriever._hydrateRequest(): hydrate requestedUser on each childRequests entry and promote requestedDate to top level - getRequestStatus() (server + client): aggregate status flags from childRequests[] when top-level properties are absent - Client date display: fallback to childRequests[0].requestedDate - Add 18 unit tests covering childRequests hydration, status aggregation, and date promotion Closes #53
This commit is contained in:
@@ -183,3 +183,152 @@ describe('arrRetrieverRegistry', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('OmbiRetriever._hydrateRequest', () => {
|
||||
let retriever;
|
||||
|
||||
beforeEach(() => {
|
||||
retriever = new OmbiRetriever({
|
||||
id: 'ombi-test',
|
||||
name: 'Test Ombi',
|
||||
url: 'http://localhost:5000',
|
||||
apiKey: 'test-key'
|
||||
});
|
||||
|
||||
// Seed the userMap cache
|
||||
retriever.cache.userMap.set('user-1', {
|
||||
id: 'user-1',
|
||||
userName: 'testuser',
|
||||
alias: 'TestUser',
|
||||
userAlias: 'TestUser',
|
||||
normalizedUserName: 'testuser'
|
||||
});
|
||||
retriever.cache.userMap.set('user-2', {
|
||||
id: 'user-2',
|
||||
userName: 'adminuser',
|
||||
alias: 'AdminUser',
|
||||
userAlias: 'AdminUser',
|
||||
normalizedUserName: 'adminuser'
|
||||
});
|
||||
});
|
||||
|
||||
it('hydrates top-level requestedUserId', () => {
|
||||
const req = {
|
||||
id: 1,
|
||||
requestedUserId: 'user-1',
|
||||
requestedUser: {}
|
||||
};
|
||||
const result = retriever._hydrateRequest(req);
|
||||
expect(result.requestedUser.userName).toBe('testuser');
|
||||
expect(result.requestedUser.alias).toBe('TestUser');
|
||||
});
|
||||
|
||||
it('hydrates childRequests requestedUserId (TV requests)', () => {
|
||||
const req = {
|
||||
id: 3,
|
||||
title: 'Test Show',
|
||||
requestedUserId: 'user-1',
|
||||
requestedUser: {},
|
||||
childRequests: [
|
||||
{
|
||||
id: 10,
|
||||
requestedUserId: 'user-2',
|
||||
requestedUser: {}
|
||||
}
|
||||
]
|
||||
};
|
||||
const result = retriever._hydrateRequest(req);
|
||||
expect(result.requestedUser.userName).toBe('testuser');
|
||||
expect(result.childRequests[0].requestedUser.userName).toBe('adminuser');
|
||||
expect(result.childRequests[0].requestedUser.alias).toBe('AdminUser');
|
||||
});
|
||||
|
||||
it('promotes requestedDate from childRequests to top level', () => {
|
||||
const req = {
|
||||
id: 3,
|
||||
title: 'Test Show',
|
||||
childRequests: [
|
||||
{
|
||||
id: 10,
|
||||
requestedDate: '2026-05-15T10:00:00.000Z'
|
||||
}
|
||||
]
|
||||
};
|
||||
const result = retriever._hydrateRequest(req);
|
||||
expect(result.requestedDate).toBe('2026-05-15T10:00:00.000Z');
|
||||
expect(result.childRequests[0].requestedDate).toBe('2026-05-15T10:00:00.000Z');
|
||||
});
|
||||
|
||||
it('does not overwrite existing top-level requestedDate', () => {
|
||||
const req = {
|
||||
id: 3,
|
||||
requestedDate: '2026-01-01T00:00:00.000Z',
|
||||
childRequests: [
|
||||
{
|
||||
id: 10,
|
||||
requestedDate: '2026-05-15T10:00:00.000Z'
|
||||
}
|
||||
]
|
||||
};
|
||||
const result = retriever._hydrateRequest(req);
|
||||
expect(result.requestedDate).toBe('2026-01-01T00:00:00.000Z');
|
||||
});
|
||||
|
||||
it('handles PascalCase RequestedDate from childRequests', () => {
|
||||
const req = {
|
||||
id: 3,
|
||||
childRequests: [
|
||||
{
|
||||
id: 10,
|
||||
RequestedDate: '2026-06-01T12:00:00.000Z'
|
||||
}
|
||||
]
|
||||
};
|
||||
const result = retriever._hydrateRequest(req);
|
||||
expect(result.requestedDate).toBe('2026-06-01T12:00:00.000Z');
|
||||
});
|
||||
|
||||
it('returns unmodified request when no hydration needed', () => {
|
||||
const req = {
|
||||
id: 1,
|
||||
title: 'Test Movie',
|
||||
requestedUser: { userName: 'existing', alias: 'Existing' }
|
||||
};
|
||||
const result = retriever._hydrateRequest(req);
|
||||
expect(result).toEqual(req);
|
||||
});
|
||||
|
||||
it('handles null childRequests gracefully', () => {
|
||||
const req = {
|
||||
id: 3,
|
||||
childRequests: null
|
||||
};
|
||||
const result = retriever._hydrateRequest(req);
|
||||
expect(result).toEqual(req);
|
||||
});
|
||||
|
||||
it('handles empty childRequests gracefully', () => {
|
||||
const req = {
|
||||
id: 3,
|
||||
childRequests: []
|
||||
};
|
||||
const result = retriever._hydrateRequest(req);
|
||||
expect(result).toEqual(req);
|
||||
});
|
||||
|
||||
it('skips child hydration when child already has valid requestedUser', () => {
|
||||
const req = {
|
||||
id: 3,
|
||||
childRequests: [
|
||||
{
|
||||
id: 10,
|
||||
requestedUserId: 'user-1',
|
||||
requestedUser: { userName: 'already_set', alias: 'AlreadySet' }
|
||||
}
|
||||
]
|
||||
};
|
||||
const result = retriever._hydrateRequest(req);
|
||||
expect(result.childRequests[0].requestedUser.userName).toBe('already_set');
|
||||
expect(result.childRequests[0].requestedUser.alias).toBe('AlreadySet');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,6 +58,40 @@ describe('getRequestStatus', () => {
|
||||
expect(getRequestStatus(makeRequest({ denied: true, approved: true }))).toBe('denied');
|
||||
expect(getRequestStatus(makeRequest({ approved: true, requested: true }))).toBe('approved');
|
||||
});
|
||||
|
||||
it('returns available from childRequests when top-level is absent (TV)', () => {
|
||||
expect(getRequestStatus({ childRequests: [{ available: true }] })).toBe('available');
|
||||
});
|
||||
|
||||
it('returns denied from childRequests when top-level is absent (TV)', () => {
|
||||
expect(getRequestStatus({ childRequests: [{ denied: true }] })).toBe('denied');
|
||||
});
|
||||
|
||||
it('returns approved from childRequests when top-level is absent (TV)', () => {
|
||||
expect(getRequestStatus({ childRequests: [{ approved: true }] })).toBe('approved');
|
||||
});
|
||||
|
||||
it('returns pending from childRequests when top-level is absent (TV)', () => {
|
||||
expect(getRequestStatus({ childRequests: [{ requested: true }] })).toBe('pending');
|
||||
});
|
||||
|
||||
it('follows priority inside childRequests: available > denied > approved > pending', () => {
|
||||
expect(getRequestStatus({ childRequests: [
|
||||
{ available: true, denied: true },
|
||||
{ approved: true }
|
||||
]})).toBe('available');
|
||||
expect(getRequestStatus({ childRequests: [
|
||||
{ denied: true, approved: true },
|
||||
{ requested: true }
|
||||
]})).toBe('denied');
|
||||
expect(getRequestStatus({ childRequests: [
|
||||
{ approved: true, requested: true }
|
||||
]})).toBe('approved');
|
||||
});
|
||||
|
||||
it('returns unknown for TV request with empty childRequests', () => {
|
||||
expect(getRequestStatus({ childRequests: [] })).toBe('unknown');
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -130,6 +130,34 @@ describe('ombiHelpers', () => {
|
||||
};
|
||||
expect(extractRequestedUser(req)).toBe('child_user');
|
||||
});
|
||||
|
||||
it('recursively extracts user from childRequests requestedUser object (hydrated TV)', () => {
|
||||
const req = {
|
||||
childRequests: [
|
||||
{},
|
||||
{ requestedUser: { userName: 'tv_user', alias: 'tv_alias' } }
|
||||
]
|
||||
};
|
||||
expect(extractRequestedUser(req)).toBe('tv_alias');
|
||||
});
|
||||
|
||||
it('recursively extracts user from childRequests requestedUser as string', () => {
|
||||
const req = {
|
||||
childRequests: [
|
||||
{ requestedUser: 'string_user' }
|
||||
]
|
||||
};
|
||||
expect(extractRequestedUser(req)).toBe('string_user');
|
||||
});
|
||||
|
||||
it('extracts user from deeply nested childRequests with requestedByAlias fallback', () => {
|
||||
const req = {
|
||||
childRequests: [
|
||||
{ requestedByAlias: 'deep_alias' }
|
||||
]
|
||||
};
|
||||
expect(extractRequestedUser(req)).toBe('deep_alias');
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterRequestsByUser', () => {
|
||||
|
||||
Reference in New Issue
Block a user