Skip to content

Commit

Permalink
Add unit tests for Stripe and Houston (#2093)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmhewitt authored Oct 17, 2023
1 parent 0af2448 commit a51fa4f
Show file tree
Hide file tree
Showing 13 changed files with 1,108 additions and 236 deletions.
5 changes: 3 additions & 2 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ add_project_arguments(['--vapidir', vapi_dir], language: 'vala')

glib = dependency ('glib-2.0')
gobject = dependency ('gobject-2.0')
gio = dependency ('gio-2.0')
gee = dependency ('gee-0.8')
gtk = dependency ('gtk+-3.0', version: '>=3.10')
granite = dependency ('granite', version: '>=6.0.0')
Expand All @@ -38,16 +39,16 @@ dbus = dependency ('dbus-1')
core_deps = [
glib,
gobject,
gio,
json,
]

dependencies = core_deps + [
gee,
gtk,
granite,
handy,
appstream,
libsoup,
json,
flatpak,
xml,
polkit,
Expand Down
1 change: 1 addition & 0 deletions po/POTFILES
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ src/Core/Job.vala
src/Core/Package.vala
src/Core/PackageKitBackend.vala
src/Core/ScreenshotCache.vala
src/Core/Stripe.vala
src/Core/Task.vala
src/Core/UbuntuDriversBackend.vala
src/Core/UpdateManager.vala
Expand Down
182 changes: 182 additions & 0 deletions src/Core/Houston.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Copyright 2023 elementary, Inc. (https://elementary.io)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/

public errordomain AppCenterCore.HoustonError {
MISSING_PARAMETERS,
NETWORK_ERROR,
SERVER_ERROR,
}

public class AppCenterCore.Houston {
public const string HOUSTON_URI = "https://developer.elementary.io/api/payment/%s";

public class PaymentRequest : Object {
public string app_id { get; construct; }
public string stripe_key { get; construct; }
public string token { get; construct; }
public string email { get; construct; }
public int amount { get; construct; }

public PaymentRequest (string app_id, string stripe_key, string token, string email, int amount) {
Object (
app_id: app_id,
stripe_key: stripe_key,
token: token,
email: email,
amount: amount
);
}

/*
* Build the JSON payload in the following format:
* {
* "data": {
* "key": "stripe_key",
* "token": "stripe_token",
* "email": "user_email",
* "amount": 100,
* "currency": "USD"
* }
* }
*/
public string _build_payload () {
var builder = new Json.Builder ()
.begin_object ()
.set_member_name ("data")
.begin_object ()
.set_member_name ("key")
.add_string_value (stripe_key)
.set_member_name ("token")
.add_string_value (token)
.set_member_name ("email")
.add_string_value (email)
.set_member_name ("amount")
.add_int_value (amount)
.set_member_name ("currency")
.add_string_value ("USD")
.end_object ()
.end_object ();

Json.Generator generator = new Json.Generator ();
Json.Node root = builder.get_root ();
generator.set_root (root);

return generator.to_data (null);
}

public async void send (HttpClient client) throws HoustonError {
string uri = HOUSTON_URI.printf (app_id);

var payload = _build_payload ();
var headers = new GLib.HashTable<string, string> (str_hash, str_equal);

headers.insert ("Accepts", "application/vnd.api+json");
headers.insert ("Content-Type", "application/vnd.api+json");

AppCenterCore.HttpClient.Response? response = null;
try {
response = yield client.post (uri, payload, headers);
} catch (Error e) {
throw new HoustonError.NETWORK_ERROR ("Unable to complete payment, please try again later. Error detail: %s".printf (e.message));
}

var parser = new Json.Parser ();
Json.Node? root = null;

debug ("Response from Houston: %s".printf (response.body));

try {
parser.load_from_data (response.body);
root = parser.get_root ();
} catch (Error e) {
throw new HoustonError.SERVER_ERROR ("Unable to complete payment, please try again later. Error detail: %s".printf (e.message));
}

if (root == null) {
throw new HoustonError.SERVER_ERROR ("Unable to complete payment, please try again later.");
}

if (root.get_object ().has_member ("errors")) {
throw new HoustonError.SERVER_ERROR ("Unable to complete payment, please try again later.");
}
}
}

public class PaymentRequestBuilder {
private string? _app_id = null;
private string? _stripe_key = null;
private string? _token = null;
private string? _email = null;
private int? _amount = null;

/**
* @param app_id AppCenter application ID
*/
public PaymentRequestBuilder app_id (string app_id) {
this._app_id = app_id;
return this;
}

/**
* @param token Stripe bearer token
*/
public PaymentRequestBuilder stripe_key (string stripe_key) {
this._stripe_key = stripe_key;
return this;
}

/**
* @param token Stripe card token
*/
public PaymentRequestBuilder token (string token) {
this._token = token;
return this;
}

/**
* @param email Email address of the user
*/
public PaymentRequestBuilder email (string email) {
this._email = email;
return this;
}

/**
* @param amount Amount in cents
*/
public PaymentRequestBuilder amount (int amount) {
this._amount = amount;
return this;
}

/**
* Build the PaymentRequest object
*
* @return PaymentRequest object
* @throws HoustonError.MISSING_PARAMETERS if any of the required parameters are missing
*/
public PaymentRequest build () throws HoustonError {
if (_app_id == null || _stripe_key == null || _token == null || _email == null || _amount == null) {
throw new HoustonError.MISSING_PARAMETERS ("Missing required parameters");
}

return new PaymentRequest (_app_id, _stripe_key, _token, _email, _amount);
}
}
}
37 changes: 37 additions & 0 deletions src/Core/HttpClient.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2023 elementary, Inc. (https://elementary.io)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/

public interface AppCenterCore.HttpClient : Object {
public class Response : GLib.Object {
public string? body { get; set; }
public GLib.HashTable<string, string>? headers { get; set; }
public uint status_code { get; set; }
}

/*
* Perform a HTTP POST request
*
* @param url The URL to post to
* @param data The data to post
* @param headers The headers to send
* @return The response body
* @throws IOError
*/
public abstract async Response post (string url, string data, GLib.HashTable<string, string>? headers = null) throws Error;
}
57 changes: 57 additions & 0 deletions src/Core/SoupClient.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2023 elementary, Inc. (https://elementary.io)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/

public class AppCenterCore.SoupClient : Object, AppCenterCore.HttpClient {
public async AppCenterCore.HttpClient.Response post (string url, string data, GLib.HashTable<string, string>? headers = null) throws Error {
var session = new Soup.Session ();
var message = new Soup.Message ("POST", url);

if (headers != null) {
headers.foreach ((key, value) => {
message.request_headers.append (key, value);
});
}

message.request_headers.append ("User-Agent", "AppCenterCore.SoupClient/1.0");
message.request_body.append_take (data.data);

var response = yield session.send_async (message);
var result = new StringBuilder ();
var buffer = new uint8[1024];
while (true) {
var read = yield response.read_async (buffer);
if (read == 0) {
break;
}

result.append_len ((string)buffer, read);
}

var response_headers = new GLib.HashTable<string, string> (str_hash, str_equal);
message.response_headers.foreach ((name, value) => {
response_headers.set (name, value);
});

return new AppCenterCore.HttpClient.Response () {
status_code = message.status_code,
headers = response_headers,
body = result.str
};
}
}
Loading

0 comments on commit a51fa4f

Please sign in to comment.