Merge branch 'develop' into stable

This commit is contained in:
2022-02-02 11:20:34 -05:00
12 changed files with 133 additions and 226 deletions

View File

@@ -98,9 +98,9 @@
<mainClass>com.inteligr8.buxfer.CLI</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<descriptors>
<descriptor>${basedir}/src/assembly/resources/assembly.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>

View File

@@ -0,0 +1,27 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<id>full</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
<containerDescriptorHandlers>
<containerDescriptorHandler>
<handlerName>metaInf-services</handlerName>
</containerDescriptorHandler>
</containerDescriptorHandlers>
</assembly>

View File

@@ -78,6 +78,7 @@ public class InvestNormalizeCLI {
"Investment / Security / " + ptx.getSecuritySymbol(), null);
return 1;
} catch (IllegalArgumentException iae) {
logger.trace("failed to parse: " + iae.getMessage(), iae);
// try another
}
}

View File

@@ -1,5 +1,6 @@
package com.inteligr8.buxfer;
import java.text.NumberFormat;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -50,14 +51,14 @@ public class SofiInvestTransaction implements ParsedTransaction {
this.throttle();
this.securities = this.estimateSecurites();
this.perSecurityPrice = this.estimatePrice(securities);
this.perSecurityPrice = this.estimatePrice(this.securities);
String action = Type.Buy.equals(type) ? "Bought" : "Sold";
this.description = new StringBuilder()
.append(action).append(' ')
.append(this.securities == null ? "NaN" : NumberFormatFactory.getSecuritiesFormatter().format(this.securities)).append(' ')
.append(this.getRange(NumberFormatFactory.getSecuritiesFormatter(), this.securities)).append(' ')
.append(this.symbol).append(" @ ")
.append(this.perSecurityPrice == null ? "NaN" : NumberFormatFactory.getPriceFormatter().format(this.perSecurityPrice)).toString();
.append(this.getRange(NumberFormatFactory.getPriceFormatter(), this.perSecurityPrice)).toString();
}
}
@@ -159,5 +160,15 @@ public class SofiInvestTransaction implements ParsedTransaction {
else if (this.perSecurityPrice.isPoint()) return this.perSecurityPrice.getFrom().doubleValue();
else return this.perSecurityPrice.getMidpoint();
}
private String getRange(NumberFormat formatter, NumericRange<? extends Number> range) {
if (range == null) {
return "NaN";
} else if (range.isPoint()) {
return formatter.format(range.getFrom().doubleValue());
} else {
return formatter.format(range.getFrom().doubleValue()) + "-" + formatter.format(range.getTo().doubleValue());
}
}
}

View File

@@ -1,35 +0,0 @@
package com.inteligr8.buxfer;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import com.inteligr8.rs.ClientConfiguration;
public abstract class ConditionalIT {
public abstract ClientConfiguration getConfiguration();
public boolean hostExists() {
String uri = this.getConfiguration().getBaseUrl();
HttpUriRequest request = RequestBuilder.get()
.setUri(uri)
.build();
HttpClient client = HttpClientBuilder.create()
.setRedirectStrategy(DefaultRedirectStrategy.INSTANCE)
.build();
try {
HttpResponse response = client.execute(request);
return response.getStatusLine().getStatusCode() < 300;
} catch (Exception e) {
return false;
}
}
}

View File

@@ -1,176 +0,0 @@
package com.inteligr8.buxfer;
import java.time.LocalDate;
import java.util.Collection;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
import com.inteligr8.buxfer.api.CommandApi;
import com.inteligr8.buxfer.model.Account;
import com.inteligr8.buxfer.model.ArrayResponse;
import com.inteligr8.buxfer.model.BaseResponse;
import com.inteligr8.buxfer.model.BaseResponse.Status;
import com.inteligr8.buxfer.model.Budget;
import com.inteligr8.buxfer.model.Item;
import com.inteligr8.buxfer.model.NamedItem;
import com.inteligr8.buxfer.model.Reminder;
import com.inteligr8.buxfer.model.Tag;
import com.inteligr8.buxfer.model.Transaction;
import com.inteligr8.buxfer.model.TransactionsResponse;
public abstract class ConnectionClientIT extends ConditionalIT {
public abstract BuxferPublicRestApi getClient();
private CommandApi api;
public boolean fullTest() {
return false;
//return this.hostExists();
}
@BeforeEach
public void getApi() {
this.api = this.getClient().getCommandApi();
}
@Test
@EnabledIf("hostExists")
public void testTransactions() {
TransactionsResponse response = this.api.getTransactions(
LocalDate.of(2021, 12, 1),
LocalDate.of(2021, 12, 31),
null,
1
).getResponse();
this.assertArrayNotEmpty(response);
Assertions.assertTrue(response.getTotalItems() > 0L);
this.assertItems(response.getItems());
}
@Test
@EnabledIf("hostExists")
public void testAccounts() {
ArrayResponse<? extends Item> response = this.api.getAccounts().getResponse();
this.assertArrayNotEmpty(response);
this.assertItems(response.getItems());
}
@Test
@EnabledIf("hostExists")
public void testTags() {
ArrayResponse<? extends Item> response = this.api.getTags().getResponse();
this.assertArrayNotEmpty(response);
this.assertItems(response.getItems());
}
@Test
@EnabledIf("hostExists")
public void testBudgets() {
ArrayResponse<? extends Item> response = this.api.getBudgets().getResponse();
this.assertArrayNotEmpty(response);
this.assertItems(response.getItems());
}
@Test
@EnabledIf("hostExists")
public void testReminders() {
ArrayResponse<? extends Item> response = this.api.getReminders().getResponse();
this.assertArrayNotEmpty(response);
this.assertItems(response.getItems());
}
@Test
@EnabledIf("fullTest")
public void testGroups() {
ArrayResponse<? extends Item> response = this.api.getGroups().getResponse();
this.assertArrayEmpty(response);
}
@Test
@EnabledIf("fullTest")
public void testContacts() {
ArrayResponse<? extends Item> response = this.api.getContacts().getResponse();
this.assertArrayEmpty(response);
}
private void assertOk(BaseResponse response) {
Assertions.assertNotNull(response);
Assertions.assertEquals(Status.OK, response.getStatus());
Assertions.assertNull(response.getError());
}
private void assertArrayEmpty(ArrayResponse<?> response) {
this.assertOk(response);
Assertions.assertNotNull(response.getItems());
Assertions.assertTrue(response.getItems().isEmpty());
}
private void assertArrayNotEmpty(ArrayResponse<?> response) {
this.assertOk(response);
Assertions.assertNotNull(response.getItems());
Assertions.assertFalse(response.getItems().isEmpty());
}
private void assertItem(Item item) {
Assertions.assertTrue(item.getId() > 0);
}
private void assertNamedItem(NamedItem item) {
this.assertItem(item);
Assertions.assertNotNull(item.getName());
Assertions.assertNotEquals(0, item.getName().length());
}
private void assertSpecificItem(Item item) {
if (item instanceof Transaction) {
this.assertTransaction((Transaction)item);
} else if (item instanceof Budget) {
this.assertBudget((Budget)item);
} else if (item instanceof Reminder) {
this.assertReminder((Reminder)item);
} else if (item instanceof Tag) {
this.assertTag((Tag)item);
} else if (item instanceof Account) {
this.assertAccount((Account)item);
}
}
private void assertTransaction(Transaction item) {
this.assertItem(item);
Assertions.assertNotNull(item.getType());
Assertions.assertNotNull(item.getAmount());
Assertions.assertNotNull(item.getDate());
}
private void assertAccount(Account item) {
this.assertNamedItem(item);
Assertions.assertNotNull(item.getBank());
Assertions.assertNotEquals(0, item.getBank().length());
Assertions.assertNotNull(item.getBalance());
}
private void assertTag(Tag item) {
this.assertNamedItem(item);
}
private void assertBudget(Budget item) {
this.assertNamedItem(item);
}
private void assertReminder(Reminder item) {
this.assertNamedItem(item);
Assertions.assertNotNull(item.getStartDate());
Assertions.assertTrue(item.getStartDate().isAfter(LocalDate.of(2010, 1, 1)));
}
private void assertItems(Collection<? extends Item> items) {
for (Item item : items) {
this.assertSpecificItem(item);
}
}
}

View File

@@ -1,6 +1,7 @@
package com.inteligr8.buxfer;
import com.inteligr8.buxfer.api.CommandApi;
import com.inteligr8.buxfer.api.SecurityApi;
/**
* This interface consolidates the JAX-RS APIs available in the Buxfer Public
@@ -10,6 +11,8 @@ import com.inteligr8.buxfer.api.CommandApi;
*/
public interface BuxferPublicRestApi {
SecurityApi getSecurityApi();
CommandApi getCommandApi();
}

View File

@@ -1,7 +1,10 @@
package com.inteligr8.buxfer.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
@JsonRootName("response")
public class BaseResponse {
public enum Status {
@@ -13,7 +16,10 @@ public class BaseResponse {
private Status status;
@JsonProperty(value = "error_description", required = false)
private String error;
private String errorDesc;
@JsonProperty(required = false)
private Error error;
@@ -25,12 +31,29 @@ public class BaseResponse {
this.status = status;
}
public String getError() {
public String getErrorDescription() {
return this.errorDesc;
}
public void setErrorDescription(String errorDesc) {
this.errorDesc = errorDesc;
}
public Error getError() {
return this.error;
}
public void setError(String error) {
public void setError(Error error) {
this.error = error;
}
@JsonIgnore
public String getErrorDescriptionOrMessage() {
if (this.error != null && this.error.getMessage() != null) {
return this.error.getMessage();
} else {
return this.errorDesc;
}
}
}

View File

@@ -0,0 +1,31 @@
package com.inteligr8.buxfer.model;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Error {
@JsonProperty
private String type;
@JsonProperty
private String message;
public String getType() {
return this.type;
}
public void setType(String type) {
this.type = type;
}
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@@ -9,15 +9,19 @@ import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.inteligr8.buxfer.model.Response;
import com.inteligr8.buxfer.model.TokenResponse;
import com.inteligr8.rs.AuthorizationFilter;
public class BuxferAuthorizationFilter implements AuthorizationFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private String email;
private String password;
private String token;
@@ -43,11 +47,13 @@ public class BuxferAuthorizationFilter implements AuthorizationFilter {
protected void requestToken(ClientRequestContext requestContext) {
UriBuilder loginUri = UriBuilder.fromUri(requestContext.getUri())
.replacePath("/api/login");
.replacePath("/api/login")
.replaceQuery(null);
Form form = new Form();
form.param("email", this.email);
form.param("password", this.password);
Entity<Form> formEntity = Entity.form(form);
GenericType<Response<TokenResponse>> responseType = new GenericType<Response<TokenResponse>>() {};
@@ -56,16 +62,16 @@ public class BuxferAuthorizationFilter implements AuthorizationFilter {
.target(loginUri)
.request()
.accept(MediaType.APPLICATION_JSON)
.post(Entity.form(form), responseType)
.getResponse();
.post(formEntity, responseType).getResponse();
if (!com.inteligr8.buxfer.model.BaseResponse.Status.OK.equals(response.getStatus()))
throw new WebApplicationException(response.getError(), Status.UNAUTHORIZED.getStatusCode());
throw new NotAuthorizedException(response.getErrorDescriptionOrMessage(), response);
this.token = response.getToken();
this.logger.debug("received access token: {} = > {}", this.email, this.token);
} catch (NotAuthorizedException nae) {
throw nae;
} catch (WebApplicationException wae) {
throw new NotAuthorizedException("Indirect due to non-authorization failure: " + wae.getMessage(), wae);
throw new NotAuthorizedException("Indirect due to non-authorization failure: [" + wae.getResponse().getStatus() + "]", wae);
}
}

View File

@@ -1,6 +1,7 @@
package com.inteligr8.buxfer;
import com.inteligr8.buxfer.api.CommandApi;
import com.inteligr8.buxfer.api.SecurityApi;
import com.inteligr8.rs.Client;
/**
@@ -21,6 +22,10 @@ public class BuxferPublicRestApiImpl implements BuxferPublicRestApi {
return this.client.getApi(apiClass);
}
public SecurityApi getSecurityApi() {
return this.client.getApi(SecurityApi.class);
}
public CommandApi getCommandApi() {
return this.client.getApi(CommandApi.class);
}

View File

@@ -0,0 +1,11 @@
# Authentication
# @name auth
POST https://www.buxfer.com/api/login
Content-type: application/x-www-form-urlencoded
email=bmlong137@gmail.com
&password=mnM9zh,_n/-w
@token = {{auth.response.body.response.token}}