Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Watt-time credentials #41

Merged
merged 2 commits into from
Mar 1, 2024
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
10 changes: 5 additions & 5 deletions src/__mocks__/watt-time/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as DATA from './data.json';

export function getMockResponse(url: string, data: any) {
export function getMockResponse(url: string) {
switch (url) {
case 'https://api2.watttime.org/v2/login':
if (
data?.auth?.username === 'test1' &&
data?.auth?.password === 'test2'
process.env.WATT_TIME_USERNAME === 'test1' &&
process.env.WATT_TIME_PASSWORD === 'test2'
) {
return Promise.resolve({
status: 200,
Expand All @@ -21,8 +21,8 @@ export function getMockResponse(url: string, data: any) {

case 'https://apifail.watttime.org/v2/login': {
if (
data?.auth?.username === 'test1' &&
data?.auth?.password === 'test2'
process.env.WATT_TIME_USERNAME === 'test1' &&
process.env.WATT_TIME_PASSWORD === 'test2'
) {
return Promise.resolve({
status: 200,
Expand Down
54 changes: 25 additions & 29 deletions src/__tests__/unit/lib/watt-time/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@ describe('lib/watt-time: ', () => {

describe('execute(): ', () => {
it('returns a result with valid data.', async () => {
const output = WattTimeGridEmissions({
username: 'test1',
password: 'test2',
});
process.env.WATT_TIME_USERNAME = 'test1';
process.env.WATT_TIME_PASSWORD = 'test2';

const output = WattTimeGridEmissions();
const result = await output.execute([
{
geolocation: '37.7749,-122.4194',
Expand All @@ -56,14 +55,13 @@ describe('lib/watt-time: ', () => {
it('throws an error when `token` is missing.', async () => {
const errorMessage =
'WattTimeAPI(authorization): Missing token in response. Invalid credentials provided.';
process.env.WATT_TIME_USERNAME = 'ENV_WATT_USERNAME';
process.env.WATT_TIME_PASSWORD = 'ENV_WATT_PASSWORD';

expect.assertions(2);

try {
const output = WattTimeGridEmissions({
username: 'ENV_WATT_USERNAME',
password: 'ENV_WATT_PASSWORD',
});
const output = WattTimeGridEmissions();
await output.execute([
{
geolocation: '37.7749,-122.4194',
Expand All @@ -79,14 +77,11 @@ describe('lib/watt-time: ', () => {

it('throws an error when credentials are missing.', async () => {
const errorMessage =
'"username" parameter is required. Error code: invalid_type.,"password" parameter is required. Error code: invalid_type.';

'WattTimeAPI(authorization): Missing token in response. Invalid credentials provided.';
expect.assertions(2);

try {
const output = WattTimeGridEmissions({
token: 'ENV_WATT_TOKEN',
});
const output = WattTimeGridEmissions();
await output.execute([
{
geolocation: '37.7749,-122.4194',
Expand All @@ -95,16 +90,16 @@ describe('lib/watt-time: ', () => {
},
]);
} catch (error) {
expect(error).toBeInstanceOf(InputValidationError);
expect(error).toEqual(new InputValidationError(errorMessage));
expect(error).toBeInstanceOf(AuthorizationError);
expect(error).toEqual(new AuthorizationError(errorMessage));
}
});

it('throws an error when initialize with wrong username / password.', async () => {
process.env.WATT_TIME_USERNAME = 'test1';
process.env.WATT_TIME_PASSWORD = 'test2';
const output = WattTimeGridEmissions({
baseUrl: 'https://apifail.watttime.org/v2',
username: 'test1',
password: 'test2',
});

expect.assertions(1);
Expand All @@ -125,10 +120,10 @@ describe('lib/watt-time: ', () => {
it('throws an error when wrong `geolocation` is provided.', async () => {
const errorMessage =
'"geolocation" parameter is should be a comma-separated string consisting of `latitude` and `longitude`. Error code: invalid_string.';
const output = WattTimeGridEmissions({
username: 'test1',
password: 'test2',
});
process.env.WATT_TIME_USERNAME = 'test1';
process.env.WATT_TIME_PASSWORD = 'test2';

const output = WattTimeGridEmissions();

expect.assertions(6);

Expand Down Expand Up @@ -174,9 +169,10 @@ describe('lib/watt-time: ', () => {

it('throws an error when no data is returned by API.', async () => {
const errorMessage = 'WattTimeAPI: Invalid response from WattTime API.';
process.env.WATT_TIME_USERNAME = 'test1';
process.env.WATT_TIME_PASSWORD = 'test2';

const output = WattTimeGridEmissions({
username: 'test1',
password: 'test2',
baseUrl: 'https://apifail2.watttime.org/v2',
});

Expand All @@ -203,9 +199,10 @@ describe('lib/watt-time: ', () => {
it('throws an error when an unauthorized error occurs during data fetch.', async () => {
const errorMessage =
'WattTimeAPI: Error fetching data from WattTime API. {"status":401,"data":{"none":{}}}.';
process.env.WATT_TIME_USERNAME = 'test1';
process.env.WATT_TIME_PASSWORD = 'test2';

const output = WattTimeGridEmissions({
username: 'test1',
password: 'test2',
baseUrl: 'https://apifail3.watttime.org/v2',
});

Expand All @@ -231,11 +228,10 @@ describe('lib/watt-time: ', () => {
it('throws an error when span is more than 32 days.', async () => {
const errorMessage =
'WattTimeGridEmissions: WattTime API supports up to 32 days. Duration of 31537200 seconds is too long.';
process.env.WATT_TIME_USERNAME = 'test1';
process.env.WATT_TIME_PASSWORD = 'test2';

const output = WattTimeGridEmissions({
username: 'test1',
password: 'test2',
});
const output = WattTimeGridEmissions();

try {
await output.execute([
Expand Down
45 changes: 17 additions & 28 deletions src/lib/watt-time/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,26 @@ Limitations:

WattTime API requires activation of subscription before usage. Please refer to the [WattTime website](https://watttime.org/docs-dev/data-plans/) for more information.

Create a `.env` file in the IF project root directory. This is where you can store your WattTime authentication details. Your `.env` file should look as follows:

**Required Parameters:**

```txt
WATT_TIME_USERNAME: <your-username>
WATT_TIME_PASSWORD: <your-password>
```
# example environment variable config , prefix the environment variables with "ENV" to load them inside the plugin.
# export WATT_TIME_USERNAME=test1
# export WATT_TIME_PASSWORD=test2

**Optional Parameter:**

```txt
WATT_TIME_TOKEN: <your-token>
```

- username: Username for the WattTime API
- ENV_WATT_TIME_USERNAME - specifying this value enables the Impact to load the value from the environment variable `WATT_TIME_USERNAME`
- password: Password for the WattTime API
- ENV_WATT_TIME_PASSWORD - specifying this value enables the Impact to load the value from the environment variable `WATT_TIME_PASSWORD`
### Plugin global config

- `base-url`: The URL for the WattTime API endpoint.

### inputs
### Inputs

**Required Parameters:**

Expand All @@ -55,10 +61,7 @@ WattTime API requires activation of subscription before usage. Please refer to t
// export WATT_TIME_USERNAME=test1
// export WATT_TIME_PASSWORD=test2
// use environment variables to configure the plugin
const output = WattTimeGridEmissions({
username: process.env.WATT_TIME_USERNAME,
password: process.env.WATT_TIME_PASSWORD,
});
const output = WattTimeGridEmissions();
const result = await output.execute([
{
timestamp: '2021-01-01T00:00:00Z',
Expand All @@ -68,20 +71,9 @@ const result = await output.execute([
]);
```

### manifest Usage

#### Environment Variable based configuration for manifest

```yaml
# environment variable config , prefix the environment variables with "ENV" to load them inside the plugin.
# export WATT_TIME_USERNAME=test1
# export WATT_TIME_PASSWORD=test2
global-config:
username: ENV_WATT_TIME_USERNAME
password: ENV_WATT_TIME_PASSWORD
```
### Manifest Usage

#### Static configuration for manifest
#### Input for manifest

```yaml
inputs:
Expand All @@ -101,9 +93,6 @@ initialize:
watt-time:
method: WattTimeGridEmissions
path: '@grnsft/if-unofficial-plugins'
global-config:
username: username
password: password
tree:
children:
child:
Expand Down
50 changes: 17 additions & 33 deletions src/lib/watt-time/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {WattTimeAPI} from './watt-time-api';
const {InputValidationError} = ERRORS;

export const WattTimeGridEmissions = (
globalConfig: ConfigParams
globalConfig?: ConfigParams
): PluginInterface => {
const metadata = {kind: 'execute'};
const wattTimeAPI = WattTimeAPI();
Expand All @@ -24,8 +24,7 @@ export const WattTimeGridEmissions = (
* Initialize authentication with global config.
*/
const initializeAuthentication = async () => {
const extractedParams = extractParamsFromConfig();
const safeConfig = validateConfig(extractedParams);
const safeConfig = validateConfig();

await wattTimeAPI.authenticate(safeConfig);
};
Expand Down Expand Up @@ -175,40 +174,25 @@ export const WattTimeGridEmissions = (
/**
* Validates static parameters.
*/
const validateConfig = (config: ConfigParams) => {
const validateConfig = () => {
const WATT_TIME_USERNAME = process.env.WATT_TIME_USERNAME;
const WATT_TIME_PASSWORD = process.env.WATT_TIME_PASSWORD;

const schema = z.object({
username: z.string(),
password: z.string(),
token: z.string().optional(),
WATT_TIME_USERNAME: z.string({
required_error: 'must be provided in .env file of `IF` root directory',
}),
WATT_TIME_PASSWORD: z.string().min(1, {
message: 'must be provided in .env file of `IF` root directory',
}),
baseUrl: z.string().optional(),
});

return validate<z.infer<typeof schema>>(schema, config);
};

/**
* Extracts username, password, and token from the provided static parameters.
* Removes the 'ENV_' prefix from the parameters if present.
*/
const extractParamsFromConfig = () => {
const username: string =
'username' in globalConfig ? (globalConfig['username'] as string) : '';
const password: string =
'password' in globalConfig ? (globalConfig['password'] as string) : '';
const token: string =
'token' in globalConfig ? (globalConfig['token'] as string) : '';

[username, password, token].map(item => removeENVPrefix(item));

return Object.assign({}, globalConfig, username, password, token);
};

/**
* Removes the 'ENV_' prefix from the provided string if present and retrieves the corresponding environment variable value.
* If the string does not start with 'ENV_', returns an empty string.
*/
const removeENVPrefix = (item: string) => {
return item.startsWith('ENV_') && (process.env[item.slice(4)] ?? '');
return validate<z.infer<typeof schema>>(schema, {
...(globalConfig || {}),
WATT_TIME_USERNAME,
WATT_TIME_PASSWORD,
});
};

return {
Expand Down
3 changes: 0 additions & 3 deletions src/lib/watt-time/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,5 @@ export interface LatitudeLongitude {
}

export type WattAuthType = {
username: string;
password: string;
token?: string;
baseUrl?: string;
};
10 changes: 6 additions & 4 deletions src/lib/watt-time/watt-time-api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as dotenv from 'dotenv';
import * as dayjs from 'dayjs';
import axios from 'axios';

Expand All @@ -20,15 +21,16 @@ export const WattTimeAPI = () => {
* Updates the token and base URL for API requests upon successful authentication.
*/
const authenticate = async (authParams: WattAuthType): Promise<void> => {
token = authParams['token'] ?? '';
dotenv.config();

token = process.env.WATT_TIME_TOKEN ?? '';
baseUrl = authParams['baseUrl'] ?? baseUrl;

if (token === '') {
const {username, password} = authParams;
const tokenResponse = await axios.get(`${baseUrl}/login`, {
auth: {
username,
password,
username: process.env.WATT_TIME_USERNAME || '',
password: process.env.WATT_TIME_PASSWORD || '',
},
});

Expand Down
Loading