Unit Test Smart Charging
ADD
test/api/SmartChargingTest.ts
+// Goal : Checks related to settings
+// Note : These unit tests use the tenant utall. This tenant should exist prior running these tests.
+// Run npm run test:createContext to create the needed utall if not present.
+
+import chai, { expect } from 'chai';
+
+import CentralServerService from './client/CentralServerService';
+import Constants from '../../src/utils/Constants';
+import ContextDefinition from './context/ContextDefinition';
+import TestData from './client/utils/TestData';
+import chaiSubset from 'chai-subset';
+import config from '../config';
+import responseHelper from '../helpers/responseHelper';
+import SmartChargingIntegration from '../integration/smart-charging/SmartChargingIntegration';
+import { SapSmartChargingSetting, SmartChargingSetting } from '../types/Setting';
+import TenantContext from './context/TenantContext';
+import ChargingStationContext from './context/ChargingStationContext';
+
+chai.use(chaiSubset);
+chai.use(responseHelper);
+
+let smartChargingImpl: SmartChargingIntegration<SmartChargingSetting>;
+
+class TestData {
+ public tenantContext: TenantContext;
+ public centralUserContext: any;
+ public centralUserService: CentralServerService;
+ public userService: CentralServerService;
+ public siteAreaContext: any;
+ public chargingStationContext: ChargingStationContext;
+
+
+ public static async setSmartChargingValidCredentials(testData) {
+ const stripeSettings = TestData.getSmartChargingSettings();
+ await TestData.saveSmartChargingSettings(testData, stripeSettings);
+ stripeSettings.secretKey = Cypher.encrypt(stripeSettings.secretKey);
+ smartChargingImpl = new StripeBillingIntegration(testData.tenantContext.getTenant().id, stripeSettings);
+ expect(smartChargingImpl).to.not.be.null;
+ }
+
+ public static async setSmartChargingCredentials(testData) {
+ const stripeSettings = TestData.getStripeSettings();
+ stripeSettings.secretKey = Cypher.encrypt('sk_test_invalid_credentials');
+ await TestData.saveBillingSettings(testData, stripeSettings);
+ billingImpl = new StripeBillingIntegration(testData.tenantContext.getTenant().id, stripeSettings);
+ expect(billingImpl).to.not.be.null;
+ }
+
+ public static async saveSmartChargingSettings(testData, stripeSettings: SapSmartChargingSetting) {
+ const tenantBillingSettings = await testData.userService.settingApi.readAll({ 'Identifier': 'billing' });
+ expect(tenantBillingSettings.data.count).to.be.eq(1);
+ const componentSetting: SettingDB = tenantBillingSettings.data.result[0];
+ componentSetting.content.type = BillingSettingsType.STRIPE;
+ componentSetting.content.stripe = stripeSettings;
+ componentSetting.sensitiveData = ['content.stripe.secretKey'];
+ await testData.userService.settingApi.update(componentSetting);
+ }
+
+ public static getStripeSettings(): StripeBillingSetting {
+ return {
+ url: config.get('billing.url'),
+ publicKey: config.get('billing.publicKey'),
+ secretKey: config.get('billing.secretKey'),
+ noCardAllowed: config.get('billing.noCardAllowed'),
+ advanceBillingAllowed: config.get('billing.advanceBillingAllowed'),
+ currency: config.get('billing.currency'),
+ immediateBillingAllowed: config.get('billing.immediateBillingAllowed'),
+ periodicBillingAllowed: config.get('billing.periodicBillingAllowed')
+ } as StripeBillingSetting;
+ }
+}
+
+describe('Smart Charging Test', function() {
+ this.timeout(30000);
+
+ before(async function() {
+ // Init values
+ testData.superCentralService = new CentralServerService(null, { email: config.get('superadmin.username'), password: config.get('superadmin.password') });
+ testData.centralService = new CentralServerService('utsmartcharg', { email: config.get('admin.username'), password: config.get('admin.password') });
+ testData.credentials.email = config.get('admin.username');
+ // Retrieve the tenant id from the name
+ const response = await testData.superCentralService.tenantApi.readAll({ 'Search' : ContextDefinition.TENANT_CONTEXTS.TENANT_SMART_CHARGING }, { limit: Constants.UNLIMITED, skip: 0 });
+ testData.credentials.tenantId = response ? response.data.result[0].id : '';
+ });
+
+ after(async function() {
+ });
+
+
+ describe('Success cases (tenant utsmartcharg)', () => {
+ it('Should connect to Smart Charging Provider', async () => {
+ const response = await testData.centralService.smartChargingApi.testConnection();
+ expect(response.data.connectionIsValid).to.be.true;
+ expect(response.data).containSubset({ status: 'Success' });
+ });
+ });
+});

MODIFY
test/api/client/CentralServerService.ts
test/api/client/CentralServerService.ts
@@ -18,6 +18,7 @@ import RegistrationTokenApi from './RegistrationTokenApi';
import SettingApi from './SettingApi';
import SiteApi from './SiteApi';
import SiteAreaApi from './SiteAreaApi';
+import SmartChargingApi from './SmartChargingApi';
import StatisticsApi from './StatisticsApi';
import TenantApi from './TenantApi';
import TenantComponents from '../../../src/types/TenantComponents';
@@ -53,6 +54,7 @@ export default class CentralServerService {
public logsApi: LogsApi;
public statisticsApi: StatisticsApi;
public billingApi: BillingApi;
+ public smartChargingApi: SmartChargingApi;
public _baseApi: BaseApi;
private _baseURL: string;
private _authenticatedUser: any;
@@ -103,6 +105,7 @@ export default class CentralServerService {
this.billingApi = new BillingApi(this.authenticatedApi);
this.assetApi = new AssetApi(this.authenticatedApi);
this.carApi = new CarApi(this.authenticatedApi);
+ this.smartChargingApi = new SmartChargingApi(this.authenticatedApi);
this.carApiSuperTenant = new CarApi(this.authenticatedSuperAdminApi);
}

ADD
test/api/client/SmartChargingApi.ts
+import Constants from './utils/Constants';
+import CrudApi from './utils/CrudApi';
+
+export default class SmartChargingApi extends CrudApi {
+ public constructor(authenticatedApi) {
+ super(authenticatedApi);
+ }
+
+ public async testConnection(params?, paging = Constants.DEFAULT_PAGING, ordering = Constants.DEFAULT_ORDERING) {
+ return await super.readAll(params, paging, ordering, '/client/api/CheckSmartChargingConnection');
+ }
+
+ public async synchronizeUsers(params?, paging = Constants.DEFAULT_PAGING, ordering = Constants.DEFAULT_ORDERING) {
+ return await super.create(params, '/client/api/BillingSynchronizeUsers');
+ }
+
+ public async synchronizeUser(params?) {
+ return await super.create(params, '/client/api/BillingSynchronizeUser');
+ }
+
+ public async forceSynchronizeUser(params?) {
+ return await super.create(params, '/client/api/BillingForceSynchronizeUser');
+ }
+
+ public async synchronizeInvoices(params?) {
+ return await super.create(params, '/client/api/BillingSynchronizeInvoices');
+ }
+}

MODIFY
test/api/context/ContextBuilder.ts
test/api/context/ContextBuilder.ts
@@ -7,6 +7,7 @@ import CentralServerService from '../client/CentralServerService';
import CompanyStorage from '../../../src/storage/mongodb/CompanyStorage';
import Constants from '../../../src/utils/Constants';
import Factory from '../../factories/Factory';
+import JsonCentralSystemServer from '../../server/ocpp/json/JsonCentralSystemServer';
import MongoDBStorage from '../../../src/storage/mongodb/MongoDBStorage';
import OCPIEndpoint from '../../../src/types/ocpi/OCPIEndpoint';
import OCPIEndpointStorage from '../../../src/storage/mongodb/OCPIEndpointStorage';
@@ -288,6 +289,9 @@ export default class ContextBuilder {
siteAreaTemplate.accessControl = siteAreaDef.accessControl;
siteAreaTemplate.siteID = site.id;
siteAreaTemplate.issuer = true;
+ siteAreaTemplate.smartCharging = siteAreaDef.smartCharging;
+ siteAreaTemplate.maximumPower = siteAreaDef.maximumPower;
+ siteAreaTemplate.numberOfPhases = siteAreaDef.numberOfPhases;
console.log(siteAreaTemplate.name);
const sireAreaID = await SiteAreaStorage.saveSiteArea(buildTenant.id, siteAreaTemplate);
const siteAreaModel = await SiteAreaStorage.getSiteArea(buildTenant.id, sireAreaID);
@@ -296,6 +300,21 @@ export default class ContextBuilder {
(chargingStation) => chargingStation.siteAreaNames && chargingStation.siteAreaNames.includes(siteAreaModel.name) === true);
// Create Charging Station for site area
for (const chargingStationDef of relevantCS) {
+ if (siteAreaModel.smartCharging) {
+ const chargingStationTemplate = Factory.chargingStation.build();
+ chargingStationTemplate.id = chargingStationDef.baseName + '-' + siteAreaModel.name;
+ console.log(chargingStationTemplate.id);
+ const connectorsDef: any = [{
+ numberOfConnectedPhase: siteAreaModel.numberOfPhases,
+ status: 'Charging'
+ },
+ {
+ numberOfConnectedPhase: siteAreaModel.numberOfPhases,
+ status: 'Charging'
+ }];
+ const newChargingStationContext = await newTenantContext.createChargingStation(chargingStationDef.ocppVersion, chargingStationTemplate, connectorsDef, siteAreaModel);
+ await siteAreaContext.addChargingStation(newChargingStationContext.getChargingStation());
+ } else {
const chargingStationTemplate = Factory.chargingStation.build();
chargingStationTemplate.id = chargingStationDef.baseName + '-' + siteAreaModel.name;
console.log(chargingStationTemplate.id);
@@ -303,6 +322,7 @@ export default class ContextBuilder {
await siteAreaContext.addChargingStation(newChargingStationContext.getChargingStation());
}
}
+ }
newTenantContext.addSiteContext(siteContext);
}
// Check if the asset tenant exists and is activated

MODIFY
test/api/context/ContextDefinition.ts
test/api/context/ContextDefinition.ts
@@ -32,6 +32,7 @@ export default class ContextDefinition {
TENANT_BILLING: 'utbilling', // Only billing and pricing component is active
TENANT_ASSET: 'utasset', // Only asset component is active
TENANT_CAR: 'utcar', // Only car component is active
+ TENANT_SMART_CHARGING: 'utsmartcharg' // Organization and Smart Charging components are active
};
static readonly SITE_CONTEXTS: any = {
@@ -44,7 +45,8 @@ export default class ContextDefinition {
static readonly SITE_AREA_CONTEXTS: any = {
NO_SITE: 'No site', // Used for unassigned Charging Station or CS in tenant with no organizations
WITH_ACL: 'withACL', // ACL is active
- WITHOUT_ACL: 'withoutACL' // ACL is inactive
+ WITHOUT_ACL: 'withoutACL', // ACL is inactive
+ WITH_SMART_CHARGING: 'withSmartCharging' // Smart Charging is active
};
static readonly CHARGING_STATION_CONTEXTS: any = {
@@ -320,7 +322,24 @@ export default class ContextDefinition {
componentSettings: {
car: {},
}
- }];
+ },
+ {
+ tenantName: ContextDefinition.TENANT_CONTEXTS.TENANT_SMART_CHARGING,
+ id: 'aaaaaaaaaaaaaaaaaaaaaab2',
+ subdomain: ContextDefinition.TENANT_CONTEXTS.TENANT_SMART_CHARGING,
+ componentSettings: {
+ organization: {},
+ smartCharging:
+ { content:
+ { type: SmartChargingSettingsType.SAP_SMART_CHARGING, sapSmartCharging:
+ { optimizerUrl: '',
+ user: '',
+ password: '' }
+ }
+ },
+ }
+ }
+ ];
// List of users created in a tenant
static readonly TENANT_USER_LIST: any = [
@@ -540,7 +559,15 @@ export default class ContextDefinition {
name: `${ContextDefinition.SITE_CONTEXTS.SITE_WITH_OTHER_USER_STOP_AUTHORIZATION}-${ContextDefinition.SITE_AREA_CONTEXTS.WITHOUT_ACL}`,
accessControl: false,
siteName: ContextDefinition.SITE_CONTEXTS.SITE_WITH_OTHER_USER_STOP_AUTHORIZATION
- }
+ },
+ { // With smart charging three phased
+ id: '5ce249a2372f0b1c8caf5442',
+ name: `${ContextDefinition.SITE_CONTEXTS.SITE_BASIC}-${ContextDefinition.SITE_AREA_CONTEXTS.WITH_SMART_CHARGING}`,
+ smartCharging: true,
+ numberOfPhases: 3,
+ maximumPower: 100000,
+ siteName: ContextDefinition.SITE_CONTEXTS.SITE_BASIC
+ },
];
// List of Charging Station created in a tenant
@@ -556,7 +583,8 @@ export default class ContextDefinition {
`${ContextDefinition.SITE_CONTEXTS.SITE_WITH_AUTO_USER_ASSIGNMENT}-${ContextDefinition.SITE_AREA_CONTEXTS.WITH_ACL}`,
`${ContextDefinition.SITE_CONTEXTS.SITE_WITH_AUTO_USER_ASSIGNMENT}-${ContextDefinition.SITE_AREA_CONTEXTS.WITHOUT_ACL}`,
`${ContextDefinition.SITE_CONTEXTS.SITE_WITH_OTHER_USER_STOP_AUTHORIZATION}-${ContextDefinition.SITE_AREA_CONTEXTS.WITH_ACL}`,
- `${ContextDefinition.SITE_CONTEXTS.SITE_WITH_OTHER_USER_STOP_AUTHORIZATION}-${ContextDefinition.SITE_AREA_CONTEXTS.WITHOUT_ACL}`]
+ `${ContextDefinition.SITE_CONTEXTS.SITE_WITH_OTHER_USER_STOP_AUTHORIZATION}-${ContextDefinition.SITE_AREA_CONTEXTS.WITHOUT_ACL}`,
+ `${ContextDefinition.SITE_CONTEXTS.SITE_BASIC}-${ContextDefinition.SITE_AREA_CONTEXTS.WITH_SMART_CHARGING}`]
},
{
baseName: ContextDefinition.CHARGING_STATION_CONTEXTS.ASSIGNED_OCPP15, // Concatenated with siteAreaName
@@ -567,7 +595,8 @@ export default class ContextDefinition {
`${ContextDefinition.SITE_CONTEXTS.SITE_WITH_AUTO_USER_ASSIGNMENT}-${ContextDefinition.SITE_AREA_CONTEXTS.WITH_ACL}`,
`${ContextDefinition.SITE_CONTEXTS.SITE_WITH_AUTO_USER_ASSIGNMENT}-${ContextDefinition.SITE_AREA_CONTEXTS.WITHOUT_ACL}`,
`${ContextDefinition.SITE_CONTEXTS.SITE_WITH_OTHER_USER_STOP_AUTHORIZATION}-${ContextDefinition.SITE_AREA_CONTEXTS.WITH_ACL}`,
- `${ContextDefinition.SITE_CONTEXTS.SITE_WITH_OTHER_USER_STOP_AUTHORIZATION}-${ContextDefinition.SITE_AREA_CONTEXTS.WITHOUT_ACL}`]
+ `${ContextDefinition.SITE_CONTEXTS.SITE_WITH_OTHER_USER_STOP_AUTHORIZATION}-${ContextDefinition.SITE_AREA_CONTEXTS.WITHOUT_ACL}`,
+ `${ContextDefinition.SITE_CONTEXTS.SITE_BASIC}-${ContextDefinition.SITE_AREA_CONTEXTS.WITH_SMART_CHARGING}`]
},
{
baseName: ContextDefinition.CHARGING_STATION_CONTEXTS.UNASSIGNED_OCPP16,

MODIFY
test/api/context/TenantContext.ts
test/api/context/TenantContext.ts
@@ -252,11 +252,12 @@ export default class TenantContext {
for (let i = 0; i < (connectorsDef ? connectorsDef.length : 2); i++) {
createdChargingStation.connectors[i] = {
connectorId: i + 1,
- status: (connectorsDef && connectorsDef.status ? connectorsDef.status : 'Available'),
- errorCode: (connectorsDef && connectorsDef.errorCode ? connectorsDef.errorCode : 'NoError'),
- timestamp: (connectorsDef && connectorsDef.timestamp ? connectorsDef.timestamp : new Date().toISOString()),
- type: (connectorsDef && connectorsDef.type ? connectorsDef.type : 'U'),
- power: (connectorsDef && connectorsDef.power ? connectorsDef.power : 22170)
+ status: (connectorsDef && connectorsDef[i].status ? connectorsDef[i].status : 'Available'),
+ errorCode: (connectorsDef && connectorsDef[i].errorCode ? connectorsDef[i].errorCode : 'NoError'),
+ timestamp: (connectorsDef && connectorsDef[i].timestamp ? connectorsDef[i].timestamp : new Date().toISOString()),
+ type: (connectorsDef && connectorsDef[i].type ? connectorsDef[i].type : 'U'),
+ power: (connectorsDef && connectorsDef[i].power ? connectorsDef[i].power : 22170),
+ numberOfConnectedPhase: (connectorsDef && connectorsDef[i].numberOfConnectedPhase ? connectorsDef[i].numberOfConnectedPhase : 3)
};
}
for (const connector of createdChargingStation.connectors) {

Unit Test Smart Charging