Skip to content

POC for Integration Testing#62

Merged
rudyfraser merged 25 commits intoblacksky-algorithms:mainfrom
TheRipperoni:integrationTesting
Mar 16, 2025
Merged

POC for Integration Testing#62
rudyfraser merged 25 commits intoblacksky-algorithms:mainfrom
TheRipperoni:integrationTesting

Conversation

@TheRipperoni
Copy link
Contributor

@TheRipperoni TheRipperoni commented Feb 15, 2025

Summary

Implementation of Integration Testing for the RSky-PDS APIs. As of now, there are only unit tests for small pieces of logic and nothing that would cover an endpoint in itself. This adds several integration tests, and supplies the starting framework to be used for writing API integration tests going forward.

Rocket method will be moved into the library rsky-pds package in order to properly test, where the main would simply call it once.

Strategy is

  1. Utilize the testcontainer crate in order to create a Postgres container for the integration tests
  2. Use diesel_migrations to setup necessary database changes
  3. Create an instance of the PDS connected to a testcontainer to perform API level testing

Components that may need either mocking, or creative solutions (Not yet implemented)

  1. The PLC Directory calls. One option for further investigation later involves creating a new testcontainer for a love PLC Directory
  2. Calls to the BSky App Server.

Other Updates

  • For CreateInviteCode and CreateAccount, created new functions that utilize the DBPool from Rocket rather than using the deprecated EstablishConnection. This is both required for testing, and best practice for functions in general. Chose to implement the methods as "v2" so other calls utilizing the old functions are undisturbed. When everything eventually shifts to the v2 calls, can simply delete the unused old, and rename.

Related Issues

#49
#24

Changes

  • Feature implementation
  • Bug fix
  • Documentation update
  • Other (please specify): Testing Implmentation and Framework

Checklist

  • I have tested the changes (including writing unit tests).
  • I confirm that my implementation aligns with the canonical Typescript implementation and/or atproto spec
  • I have updated relevant documentation.
  • I have formatted my code correctly
  • I have provided examples for how this code works or will be used

@TheRipperoni
Copy link
Contributor Author

@rudyfraser Is an approach like this something on the table?

@rudyfraser
Copy link
Member

@rudyfraser Is an approach like this something on the table?

Yeah, definitely. As long as tests can still run concurrently and the DBs are torn down after the tests complete I'm good with this approach.

@TheRipperoni
Copy link
Contributor Author

Having to address some tech debt related to establish_connection() to properly implement

@TheRipperoni TheRipperoni marked this pull request as ready for review February 18, 2025 00:59
@TheRipperoni TheRipperoni marked this pull request as draft February 18, 2025 01:00
@TheRipperoni
Copy link
Contributor Author

Will clean up and make a new pull request. Only thing last to check is how it behaves on the Github pipeline

@TheRipperoni
Copy link
Contributor Author

Came across integration-test issue as service is dependent on environment variables to startup. @rudyfraser is it possible for us to pass environment variables necessary to start an rsky-pds in the github action?

The specific erroring for this one is on "PDS_ADMIN_PASS"

@TheRipperoni TheRipperoni changed the title POC for Implementation Testing POC for Integration Testing Feb 22, 2025
rudyfraser added a commit that referenced this pull request Feb 24, 2025
Should resolve issues in #62
@rudyfraser
Copy link
Member

I added a bunch of environment vars to the GitHub workflow that should fix this but let me know if it isn't squared away.

@TheRipperoni
Copy link
Contributor Author

I added a bunch of environment vars to the GitHub workflow that should fix this but let me know if it isn't squared away.

Thanks! It looks like build is failing testing when it uses the secrets field specifically? Unsure why that is. I passed it some hard values for the time being to validate that.

Other than that, can confirm that test containers is working. Going to cleanup the PR a bit and then move it into "Ready for Review"

@TheRipperoni TheRipperoni marked this pull request as ready for review February 26, 2025 02:08
@TheRipperoni
Copy link
Contributor Author

Any feedback on this one?

@TheRipperoni
Copy link
Contributor Author

If this is ready to go before OAuth, we can have integration testing for oauth ideally

@rudyfraser
Copy link
Member

Missed this, sorry. Thanks for the ping. Will review tonight.

Copy link
Member

@rudyfraser rudyfraser left a comment

Choose a reason for hiding this comment

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

The tests look good. However, I’m not a fan of the foo_v2 pattern (I stopped commenting on it midway).

Instead, I think AccountManager should be updated from:

#[derive(Clone)]
pub struct AccountManager {}

to:

#[derive(Clone)]
pub struct AccountManager {
    pub db: Arc<DbConn>
}

Then, update the functions to be instance methods (&self) instead of static functions.

This aligns with the TypeScript implementation and helps keep things DRY.

Copy link
Member

Choose a reason for hiding this comment

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

What's the reasoning behind the foo_v2 pattern? Any reason to not refactor the code to use DbConn entirely? That's where I'd like things to go in any case and get rid of "establish_connection" where possible. It's a bit of technical debt.

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 was using v2 to avoid having to do a total rewrite of the connection pattern yet. Plan was to move all to v2, then remove the old method, and then just remove v2

Comment on lines +242 to +263
pub async fn store_refresh_token_v2(
payload: RefreshToken,
app_password_name: Option<String>,
db: &DbConn,
) -> Result<()> {
use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema;

let exp = from_micros_to_utc((payload.exp.as_millis() / 1000) as i64);

db.run(move |conn| {
insert_into(RefreshTokenSchema::refresh_token)
.values((
RefreshTokenSchema::id.eq(payload.jti),
RefreshTokenSchema::did.eq(payload.sub),
RefreshTokenSchema::appPasswordName.eq(app_password_name),
RefreshTokenSchema::expiresAt.eq(format!("{}", exp.format(RFC3339_VARIANT))),
))
.on_conflict_do_nothing() // E.g. when re-granting during a refresh grace period
.execute(conn)
})
.await?;

Copy link
Member

Choose a reason for hiding this comment

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

Similar question here on need for v2

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 was using v2 to avoid having to do a total rewrite of the connection pattern yet. Plan was to move all to v2, then remove the old method, and then just remove v2

Copy link
Member

Choose a reason for hiding this comment

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

I'd rather not have the transition period. Let me know if you'd like me to take on the migration to the v2 pattern.

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 can take this up

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can have it updated by end of the weekend

Copy link
Member

Choose a reason for hiding this comment

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

Avoid foo_v2 pattern

Copy link
Member

Choose a reason for hiding this comment

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

Avoid foo_v2 pattern

Comment on lines +31 to +56
pub async fn update_root_v2(did: String, cid: Cid, rev: String, db: &DbConn) -> Result<()> {
// @TODO balance risk of a race in the case of a long retry
use crate::schema::pds::repo_root::dsl as RepoRootSchema;

let now = rsky_common::now();

db.run(move |conn| {
insert_into(RepoRootSchema::repo_root)
.values((
RepoRootSchema::did.eq(did),
RepoRootSchema::cid.eq(cid.to_string()),
RepoRootSchema::rev.eq(rev.clone()),
RepoRootSchema::indexedAt.eq(now),
))
.on_conflict(RepoRootSchema::did)
.do_update()
.set((
RepoRootSchema::cid.eq(cid.to_string()),
RepoRootSchema::rev.eq(rev),
))
.execute(conn)
})
.await?;

Ok(())
}
Copy link
Member

Choose a reason for hiding this comment

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

avoid foo_v2

Comment on lines +111 to +119
client
.post("/xrpc/com.atproto.server.createAccount")
.header(ContentType::JSON)
.header(Header::new("Authorization", get_admin_token()))
.body(account_input.to_string())
.dispatch()
.await;

("dummyemail@rsky.com".to_string(), "password".to_string())
Copy link
Member

Choose a reason for hiding this comment

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

What happens if there's an error here in creating the account (for example, generating a DID)?

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 can add an assert. Currently it will cause the tests utilizing the function to fail as the account won't exist.

Copy link
Member

Choose a reason for hiding this comment

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

From what I can see it's only used in test_create_session. And if it isn't creating an account then what is it actually doing?

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 does create an account, if it was to fail currently the account wouldn't exist at all tests would fail

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Testing specific function to make tests easier to write

@TheRipperoni
Copy link
Contributor Author

@rudyfraser Left some comments

@TheRipperoni
Copy link
Contributor Author

The tests look good. However, I’m not a fan of the foo_v2 pattern (I stopped commenting on it midway).

Instead, I think AccountManager should be updated from:

#[derive(Clone)]

pub struct AccountManager {}

to:

#[derive(Clone)]

pub struct AccountManager {

    pub db: Arc<DbConn>

}

Then, update the functions to be instance methods (&self) instead of static functions.

This aligns with the TypeScript implementation and helps keep things DRY.

Will have an update for this later today

@TheRipperoni
Copy link
Contributor Author

The tests look good. However, I’m not a fan of the foo_v2 pattern (I stopped commenting on it midway).

Instead, I think AccountManager should be updated from:

#[derive(Clone)]
pub struct AccountManager {}

to:

#[derive(Clone)]
pub struct AccountManager {
    pub db: Arc<DbConn>
}

Then, update the functions to be instance methods (&self) instead of static functions.

This aligns with the TypeScript implementation and helps keep things DRY.

I need to check how this plays with the Rocket Fairing specifically

@TheRipperoni
Copy link
Contributor Author

Realized changing AccountManager to a state like that has some knock on effects as currently it is used in LocalViewer, which will mean LocalViewer will need updates and changes as well

@rudyfraser
Copy link
Member

Realized changing AccountManager to a state like that has some knock on effects as currently it is used in LocalViewer, which will mean LocalViewer will need updates and changes as well

Does this mean you'd like to hold off on making the changes or that you're in the middle of changes but will take some more time?

@TheRipperoni
Copy link
Contributor Author

Realized changing AccountManager to a state like that has some knock on effects as currently it is used in LocalViewer, which will mean LocalViewer will need updates and changes as well

Does this mean you'd like to hold off on making the changes or that you're in the middle of changes but will take some more time?

I was in the middle of making the changes, but realized that there are some key differences in the typescript reference.

One of them is that because it uses SQLite, it doesn't use a connection pool to establish the connection. Because LocalViewer is a Read/Write Lock, but the db connection pool is managed by Rocket, I'd like to move the function logic into the SharedLocalViewer instead and have the SharedLocalViewer be a request guard that utilizes the local viewer state.

My main concern is it bloats the pull request into even more changes. If that's acceptable, I can do it, but it will have affects

@rudyfraser
Copy link
Member

Instead of naming the new functions "foo_v2", please rename the old function "bar_legacy" and add the #[deprecated] attribute to them.

@TheRipperoni
Copy link
Contributor Author

Instead of naming the new functions "foo_v2", please rename the old function "bar_legacy" and add the #[deprecated] attribute to them.

Can do, will update later today for review

@TheRipperoni
Copy link
Contributor Author

@rudyfraser Renamed v2 and marked old functions as legacy

@rudyfraser rudyfraser merged commit 543fb2d into blacksky-algorithms:main Mar 16, 2025
4 checks passed
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.

2 participants