Skip to content

Conversation

@jonathanbataire
Copy link
Contributor

@jonathanbataire jonathanbataire commented Jan 14, 2025

Description

Current app requires rebuilding and re-deploying a app in order to change config
This approach introduces uploadable config making it easier to change configuration
The app defaults to the prebuilt config, once a new configuration is uploaded it overrides any pre-built config
Configuration can only be updated by CHT admins
Fixes #194

changes

  • introduces new api end point /api/config used with basic auth to upload config
curl -X POST https://username:password@user-management.com/api/config?domain=cht-instance.com \
     -H "Content-Type: application/json" \
     -d @config.json

@jonathanbataire jonathanbataire marked this pull request as draft January 14, 2025 07:16
@jonathanbataire jonathanbataire marked this pull request as ready for review January 14, 2025 08:04
const parentMatch = contactType.hierarchy.find(c => c.level === 1);
if (!parentMatch) {
throw new Error(`hierarchy at level 1 is required: "${contactType.name}"`);
throw new Error(`hierarchy at level 1 is required: "${contactType?.name}"`);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clean up

const contactMatch = config.contact_types.find(c => c.name === name);
public static async getContactType(name: string) : Promise<ContactType> {
const {config} = await init();
const contactMatch = config.contact_types.find(c => c?.name === name);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clean up

@jonathanbataire jonathanbataire changed the title Makes configuration uploadable thru UI Makes configuration uploadable thru API Feb 20, 2025
};

export default function getConfigByKey(key: string = 'CHIS-KE'): PartnerConfig {
export default async function getConfigByKey(key: string = 'CHIS-KE'): Promise<PartnerConfig> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what scenario would the config be invalid after upload? Isn't validity checked at upload time?


const partnerConfig = getConfigByKey(CONFIG_NAME);
const { config } = partnerConfig;
async function init(): Promise<PartnerConfig> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use getConfigByKey directly?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, isAdmin is more of a chtSession property than an action on chtSession


try {
const chtSession = Auth.createCookieSession(cookieToken);
if (req.routeOptions.url === '/api/config' && !chtSession?.isAdmin) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should add this check to the route's request handler itself

Copy link
Contributor Author

@jonathanbataire jonathanbataire Mar 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think authorization should be handled in the auth hook/middleware
but i have no strong opinion about this though i can move it
LMK


export async function readConfig(): Promise<PartnerConfig> {
try {
const fileContent = await fs.promises.readFile(uploadedConfigFilePath, 'utf-8');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see alot of calls had to be converted to async so i have to ask, Is there a synchronous equivalent to this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah sure there is but its asynchronous mainly for performance.
since this is an api/server you'd want to support concurrency with multiple requests without blocking
also config is read almost every time you make a request to the server for a page

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If It's for performance, wouldn't caching the config be more performant than reading from disk for every request?

Copy link
Contributor Author

@jonathanbataire jonathanbataire Mar 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe maybe not
Its just a single small file we just read from
but generally i wouldn't advice reading file synchronously in a server cos of its blocking nature
It more like reading db data synchronously, not advisable

};

const contactType = Config.getContactType(payload.contact_type);
const contactType = await Config.getContactType(payload.contact_type);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't look like this is still an async function


const authHeader = req.headers.authorization as string;
if (authHeader && authHeader.startsWith('Basic ')) {
if (!req.routeOptions.url?.startsWith('/api')) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When i POST the config without credentials it will try to redirect me to the /login endpoint which i think is awkward for an API. Is there any way this can be improved?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved


//if config has default config i.e a new install
//return passed domain for api config upload authentication
if (availableDomains.some(c => c.domain === 'localhost')) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the comment here, why are you explicitly checking for localhost?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

}

public static getLogoBase64() : string {
public static async getLogoBase64(): Promise<string> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't need to be async

fastify.post('/place/dob', async (req, resp) => {
const { place_type, prefix, prop_type } = req.query as any;
const contactType = Config.getContactType(place_type).contact_properties.find(prop => prop.type === prop_type);
const config = await Config.getContactType(place_type);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be reverted, doesn't look relevant to the changes in the PR


});

fastify.post('/api/config', async (req: FastifyRequest, reply: FastifyReply) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was able to post a config without any domains, which crashed the app after a logout. I was unable to post a new config after the crash happened

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gj finding this case but there will be others. i'd bet on it.
this points to a deeper problem - how do you recover in production if an invalid config crashes the app? just another reason this pattern is concerning

if (!key) {
console.log('no configuration found. Setting to default');
const defaultConfig = kenyaConfig;
defaultConfig.config.domains = [{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this override the domains in this config? Would it be better to just crash the app if the key isn't set in the environment variables since that would be a deployment config issue?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The crash scenario is just below this code block which i think is a better way of handling this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i have added default config in case env variable is not provided this can then be overridden via api

Copy link
Collaborator

@freddieptf freddieptf Mar 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

environment variables should always be provided and checked at start up. We also shouldn't be changing the config values in code

Copy link
Contributor Author

@jonathanbataire jonathanbataire Mar 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats not what im doing
i simply making the env variable optional
if not provided the app falls back to a default config which allows overwriting via api with any auth domain
other configs ie env and uploaded, will validate authenticate domain before overwriting
chck it out

try {
const jsonString: string = JSON.stringify(jsonConfigData, null, 2);
fs.writeFileSync(this.filePath, jsonString);
this.refreshConfig();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since refreshConfig is only used here, maybe you can just directly set the config here after writing it. Saves you a trip to the filesystem.

nitpick: the parameter name jsonConfigData is kinda confusing, maybe just config is fine?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets leave it there keeps it organized the writeconfig will be doing more than it describes
besides its a great action to have on the class

export default async function sessionCache(fastify: FastifyInstance) {
fastify.get('/', async (req, resp) => {
const contactTypes = Config.contactTypes();
const contactTypes = await Config.contactTypes();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer async

Copy link
Collaborator

@freddieptf freddieptf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good progress, there are some changes around having a "default config" that i feel should not be a part of this PR and require further discourse. Please create another issue for this and lets have this PR only address adding the API

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am against having a default config simply because you'd be making some assumptions of one's setup. The dev should have their environment set up correctly and provide a valid config for their setup before starting the service and if none is provided the app shouldn't start

Copy link
Contributor Author

@jonathanbataire jonathanbataire Mar 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its not meant to be used it wouldnt work its just a place holder to allow uploading config on setting up the tool without breaking the tool
its there to allow some upload config
not having it leaves a gap on how config upload workflow would go

Copy link
Contributor Author

@jonathanbataire jonathanbataire Mar 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you leave ke(or other) config as default i cant update config in my set up if i dont have admin access to ke instance
also remember config is not set only in env variable

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not having it leaves a gap on how config upload workflow would go

Isn't the workflow simply overwriting the app's current config?

if you leave ke(or other) config as default i cant update config in my set up if i dont have admin access to ke instance
also remember config is not set only in env variable

The config to load is already set via the CONFIG_NAME env variable. If no part of this api touches the env variables then how would that value change? For your case wouldn't It always be chis-ug?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

built in config file is never overwritten
a new file is created for the uploaded config
NOTE as of last week chis-ug is no longer a built in config it was removed by some PR

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would the UG app be deployed with these changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no env variable
deploy app
upload config from project repo

// TODO: Joi? Chai?
public static assertValid({ config }: PartnerConfig = partnerConfig) {
for (const contactType of config.contact_types) {
public static assertValid(config?: PartnerConfig) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the config is undefined isn't it invalid by default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure i understand this fully

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You made the parameter config optional. It shouldn't be

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was optional before

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It had a default value, that doesn't mean it was optional. I don't think you should be able to pass an undefined value to this function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats exaclty what it means :)
check this out

Config.assertValid();

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It had a default value. The default value was set outside the function, you are setting the default value inside the function and changing the function signature to allow for a null value. That's my issue here. The function is meant to validate a config that's passed to it via the parameters.

Copy link
Contributor Author

@jonathanbataire jonathanbataire Mar 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that function was being called with a undefined parameter im not changing the signiture
have you looked at the link?
it literally accepts undefined param

Copy link
Contributor Author

@jonathanbataire jonathanbataire Mar 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also the default param value was being set in the function not outside

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me know if you want to hope on a call tomorrow so we can finalize this

fastify.post('/api/config', async (req: FastifyRequest, reply: FastifyReply) => {
try {
const config = await req.body as ConfigSystem;
await Config.assertValid({ config });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertValid is not async

public static assertValid({ config }: PartnerConfig = partnerConfig) {
for (const contactType of config.contact_types) {
public static assertValid(config?: PartnerConfig) {
const { config: assertionConfig } = config || ConfigFactory.getConfigFactory().loadConfig(CONFIG_NAME);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be validating the passed config, not trying to load it in the function.

Copy link
Contributor Author

@jonathanbataire jonathanbataire Mar 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i did this cause the way it was built initially
in some cases assertValid is called without any config passed
so to minimized changes i had to cater for these scenarios
it not doing anything different from what was there only how its implemented

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some in some cases assertValid is called with any config passed

and that's exactly how it's supposed to work, to validate the config passed to the function. Please revert.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont understand before this PR this function is called without passing config into it
its what im trying to maintain here

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It had a default value that was the config loaded at startup. The equivalent behavior now would be calling loadConfig and passing that value to this function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to what is being discussed here #240 (comment)

reply.send({ message: 'configuration updated' });
return;
} catch (error) {
console.error('Route api/config: ', error);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validation errors should return status code 400

const newConfigHasExistingDomain = _.some(availableDomains,
existingDomain => _.some(assertionConfig.domains, newDomain => _.isMatch(existingDomain, newDomain)));
if (!this.isDefaultConfig() && !newConfigHasExistingDomain) {
throw Error(`Invalid configuration: 'domains' property doesn't contains any existing domain`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for not allowing the domain list to be fully updated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you have domains a,b, and c in the tool
when updated you dont want to overwrite them with x,y and z without atleast having any of a,b and c in the new config
avoid locking yourself out accidentally/intentionally

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if you only had credentials for domain d and it gets overwritten leaving domains a, b, x, y, z? Wouldn't you still be locked out?

That doesn't make the config invalid and i don't think it's something we should be checking for. Admins have access to the deployments (cht and this tool) and should be able to handle such cases so i don't see how that would be a problem

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont you think a person with credentials to a domain not part of the UMT domains shouldnt be able to overwrite domains
scenario: if i had admin access to ug instance should i be able to use the same admin access to upload config in a ke instance

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't prevent that though. If that's what you're trying to prevent then shouldn't you be checking that the domain passed in the api call exists in the config's domain list?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me explain better
if i had admin access to ug i can overwrite all domains and change them to all ke domains
this check tries to avoid this such that at least one of the new domains exited in the previous config
Overwriting all is NOT impossible this just makes it harder so its not done by accident
Overwriting all would be at least a 2 step operation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hence avoid admin locking them selves out by uploading all ke domains in a ug app and locking themselves out because they dont have ke credentials

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if i had admin access to ug i can overwrite all domains and change them to all ke domains

That's up-to the user and it's out of our hands. The user is an admin, they have ways to fix that.

scenario: if i had admin access to ug instance should i be able to use the same admin access to upload config in a ke instance

This is the scenario you should be fixing as it's a valid bug here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

honestly i dont see any problem with that check its just a precaution
that scenario is handled

throw Error(`invalid configuration: 'contact_types' property is empty`);
}

for (const contactType of assertionConfig.contact_types) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to update this and make validation very thorough making sure all required fields on the ContactType object have values. Use the object definition as a guide on what is strictly required. For eg. I can upload a ContactType without the name field even though it's required

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mainly focused on things that would crash the app

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any required fields missing from ContactType will crash the app at some point. The app functionality i.e all generated forms, heavily depends on ContactType.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i have tested this the app will only misbehave but wont crush unless there is a particular scenario im missing
if the app is misbehaving config can be update ideally it should be tested before deploying
at least for now
lets handle these other cases in a new pr trying to get this in asap its blocking work

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The app won't work correctly if a ContactType doesn't have a valid name. The data clean-up you'd set yourself up for just from this one field missing is worth having the checks in place now. This is just one example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont think cht would accept uploading data if the name is invalid/wrong
and for this is unlikely to happen given config has to go thru testing by the dev before deployment
again this wont break the app lets handle all these scenarios in another pr

checkRedisConnection();

fastify.addHook('preValidation', async (req: FastifyRequest, reply: FastifyReply) => {
if (req.routeOptions.url?.startsWith('/api') && req.method === 'POST') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be for all request methods?

Copy link
Contributor Author

@jonathanbataire jonathanbataire Mar 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is checking for credentials not all requests have credentials/authheader
only api call come with authheader

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the check you implemented apply for all api calls? Why limit it to POST calls only?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you can call this endpoint in a browser to view config

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is specific to a single endpoing, why isn't it in the route file. why does it have to awkwardly be in this hook?

'CHIS-TG': togoConfig,
'CHIS-CIV': civConfig,
'CHIS-ML': maliConfig,
DEFAULT: defaultConfig
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you just add the config for uganda back into the repo? it should can a flag like can upload to true to ensure configs on other instances are not replaceable

this.filePath = path.join(this.getConfigUploadDirectory(), 'config.json');
}

static getConfigFactory(): ConfigFactory {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is a singleton, why not just have ConfigFactory be a static class?

};

constructor() {
this.filePath = path.join(this.getConfigUploadDirectory(), 'config.json');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filePath should be explicit about what file it is pointing at. maybe uploadedConfigPath


const usingKey = key.toUpperCase();
console.log(`Using configuration: ${key}`);
const result = this.DEFAULT_CONFIG_MAP[usingKey];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const result = this.DEFAULT_CONFIG_MAP[usingKey];
this.config = this.DEFAULT_CONFIG_MAP[usingKey];

This would be cleaner without the intermittent variable result imo


private getConfigUploadDirectory(): string {
const configDir = path.join(__dirname, '..', 'config_uploads');
if (!fs.existsSync(configDir)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to have this existsSync check in writeConfig?

}

private getConfigUploadDirectory(): string {
const configDir = path.join(__dirname, '..', 'config_uploads');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what sort of scenarios have you tested to ensure proper persistence of this file in docker?

try {
const config = await req.body as ConfigSystem;
Config.assertValid({ config });
ConfigFactory.getConfigFactory().writeConfig(config);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems writeConfig should call assertValid? that feels like the right flow to ensure invalid configs cannot be written

checkRedisConnection();

fastify.addHook('preValidation', async (req: FastifyRequest, reply: FastifyReply) => {
if (req.routeOptions.url?.startsWith('/api') && req.method === 'POST') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is specific to a single endpoing, why isn't it in the route file. why does it have to awkwardly be in this hook?

return;
}

const { domain } = req.query as { [key: string]: string };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is basic auth really what we want to use? why not just have an authenticated API? imo we should require users of the API to use /login (one way to authenticate to UMT), get issued a cookie, and then include it in requests to /api. that seems straightforward and not terribly burdensome to users of the API (atm just jonathan)

import { Config, PartnerConfig } from '../src/config';
import { CONFIG_MAP } from '../src/config/config-factory';
import DEFAULT_CONFIG_MAP from '../src/config/config-factory';
import { mockSimpleContactType } from './mocks';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add tests for config-factory?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

separate config from main source code

4 participants