Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/pink-zebras-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sap-ux/fe-mockserver-core': patch
---

feat: dynamically register services defined in `Common.ValueListReferences` annotation
46 changes: 46 additions & 0 deletions packages/fe-mockserver-core/src/data/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import type {
RawMetadata,
Singleton
} from '@sap-ux/vocabularies-types';
import { join } from 'path';
import { join as joinPosix } from 'path/posix';

type NameAndNav = {
name: string;
Expand Down Expand Up @@ -241,4 +243,48 @@ export class ODataMetadata {

return keyValues;
}

public getValueListReferences(metadataPath: string) {
const references = [];
for (const entityType of this.metadata.entityTypes) {
for (const property of entityType.entityProperties) {
const rootPath = this.metadataUrl.replace('/$metadata', '');
const target = `${entityType.name}/${property.name}`;
for (const reference of property.annotations.Common?.ValueListReferences ?? []) {
const externalServiceMetadataPath = joinPosix(rootPath, reference as string).replace(
'/$metadata',
''
);
const [valueListServicePath] = externalServiceMetadataPath.split(';');
const segments = valueListServicePath.split('/');
let prefix = '/';
let currentSegment = segments.shift();
while (currentSegment !== undefined) {
const next = join(prefix, currentSegment);
if (!rootPath.startsWith(next)) {
break;
}
prefix = next;
currentSegment = segments.shift();
}
const relativeServicePath = valueListServicePath.replace(prefix, '');

const serviceRoot = join(metadataPath, '..', relativeServicePath, target);
const localPath = join(serviceRoot, `metadata.xml`);

references.push({
rootPath,
externalServiceMetadataPath: encode(externalServiceMetadataPath),
localPath: localPath,
dataPath: serviceRoot
});
}
}
}
return references;
}
}

function encode(str: string): string {
return str.replaceAll("'", '%27').replaceAll('*', '%2A');
}
25 changes: 25 additions & 0 deletions packages/fe-mockserver-core/src/data/serviceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,32 @@ export class ServiceRegistry {
} else {
metadata = await loadMetadata(mockService, processor);
}

const dataAccess = new DataAccess(mockService, metadata, this.fileLoader, this.config.logger, this);
if (metadata) {
const references = metadata.getValueListReferences(mockService.metadataPath);
await Promise.allSettled(
references.map(async (reference) => {
const exists = await this.fileLoader.exists(reference.localPath);
if (!exists) {
log.info(
`ValueList reference metadata file not found at "${reference.localPath}". Service "${reference.externalServiceMetadataPath}" will not be provided.`
);
return undefined;
}
return this.createServiceRegistration(
{
metadataPath: reference.localPath,
urlPath: reference.externalServiceMetadataPath,
generateMockData: true,
mockdataPath: reference.dataPath,
watch: false
},
log
);
})
);
}

// Register this service for cross-service access
this.registerService(mockService.urlPath, dataAccess, mockService.alias);
Expand Down
90 changes: 90 additions & 0 deletions packages/fe-mockserver-core/test/unit/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Server } from 'http';
import * as http from 'http';
import * as path from 'path';
import FEMockserver, { type MockserverConfiguration } from '../../src';
import FileSystemLoader from '../../src/plugins/fileSystemLoader';
import { getJsonFromMultipartContent, getStatusAndHeadersFromMultipartContent } from '../../test/unit/__testData/utils';
import { ODataV4Requestor } from './__testData/Requestor';

Expand Down Expand Up @@ -753,6 +754,95 @@ Group ID: $auto`
});
});

describe('services from ValueListReferences', () => {
let server: Server;
let loadFileSpy: jest.SpyInstance;
beforeAll(async function () {
const loadFile = FileSystemLoader.prototype.loadFile;
const exists = FileSystemLoader.prototype.exists;
jest.spyOn(FileSystemLoader.prototype, 'exists').mockImplementation((path): Promise<boolean> => {
if (path.includes('i_companycodestdvh') && path.includes('metadata.xml')) {
return Promise.resolve(true);
} else {
return exists(path);
}
});
loadFileSpy = jest.spyOn(FileSystemLoader.prototype, 'loadFile').mockImplementation((path): Promise<string> => {
if (path.includes('i_companycodestdvh') && path.includes('metadata.xml')) {
return Promise.resolve(`<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">
<edmx:DataServices>
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="local">
</Schema>
</edmx:DataServices>
</edmx:Edmx>`);
} else {
return loadFile(path);
}
});
const mockServer = new FEMockserver({
services: [
{
metadataPath: path.join(__dirname, 'v4', 'services', 'parametrizedSample', 'metadata.xml'),
mockdataPath: path.join(__dirname, 'v4', 'services', 'parametrizedSample'),
urlPath: '/sap/fe/core/mock/sticky',
watch: false,
generateMockData: true
}
],
annotations: [],
plugins: [],
contextBasedIsolation: true
});
await mockServer.isReady;
server = http.createServer(function onRequest(req, res) {
mockServer.getRouter()(req, res, finalHandler(req, res));
});
server.listen(33332);
});
afterAll((done) => {
server.close(done);
});

it.only('call service from ValueListReferences', async () => {
const response = await fetch(
`http://localhost:33332/sap/srvd_f4/sap/i_companycodestdvh/0001;ps=%27srvd-zrc_arcustomer_definition-0001%27;va=%27com.sap.gateway.srvd.zrc_arcustomer_definition.v0001.et-parameterz_arcustomer2.p_companycode%27/$metadata`
);

expect(response.status).toEqual(200);
expect(loadFileSpy).toHaveBeenNthCalledWith(
2,
path.join(
__dirname,
'v4',
'services',
'parametrizedSample',
'srvd_f4',
'sap',
'i_companycodestdvh',
'0001',
'CustomerParameters',
'P_CompanyCode',
'metadata.xml'
)
);
expect(loadFileSpy).not.toHaveBeenCalledWith(
path.join(
__dirname,
'v4',
'services',
'parametrizedSample',
'srvd_f4',
'sap',
'i_customer_vh',
'0001',
'CustomerType',
'Customer',
'metadata.xml'
)
);
});
});

describe('V2', function () {
let server: Server;
beforeAll(async function () {
Expand Down
Loading