mb-sync

After a lot of other stuff I’m back at it. TIL that php ENUMS are kind of hard to do assertions on in PHPUnit. assertSame and assertEquals both don’t work.

What I can do though is this:

$this->assertSame(SyncStatus::Pending->value, $syncStatusAfter->value);

#buildinpublic #mbsync

After many hours of familystuff, I’m back trying to see why one of the tests is still failing, now that my sidequest about the seeder is complete. #buildinpublic #mbsync

[Just noticed I never sent this…]

#buildinpublic #mbsync

Alright, so the seeder works and I can skip registering all the time when resetting the db. 😅

Instead I only need to run:

artisan migrate:fresh

and then:

artisan db:seed

And I’m good to go!

The code needed some trial and error to get right, but looks basically like this:

<?php

namespace Database\Seeders;

use App\Models\Blog;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Hash;

class AddAdminUserSeeder extends Seeder
{
    public function run(): void
    {
        $user = User::create([
		//...
        ]);
        $user->save();
        $blogs = [
		//...
        ];
        $user->blogs()->saveMany($blogs);
        $user->selected_blog_id = $blogs[0]->id;
        $user->save();
    }
}

It’s just really cool to me how powerful laravel’s db abstractions are. I may not know yet how to express my needs and then try to do it old-school directly in sql at first, but that it’s possible to express my needs and I can otherwise just focus on the code is so cool. #buildinpublic #mbsync

I’m working on a little side quest atm. I’m creating a database seeder that automatically adds a user to the db so that I can reset the db quicker. It’s pretty easy with laravel and I love the distinction between migrations (schema changes) and seeders (data additions) #buildinpublic #mbsync

At the moment I’m working on implementing the minimalistic sync engine - even calling it that gives it too much credit. One test is failing, it seems that the sync status is not marked as pending when we mark a post for sync. #buildinpublic #mbsync

I’ll continue my work on mb-sync. It’s always a little hard to know how much time I have - so I might need to do something else soon, but I’ll at least start. #buildinpublic #mbsync

Today I’m spending time in the library working on my notes. I thought I’d try to do a #buildinpublic kind of thing, since it was fun the last couple of days to post about what I’m doing as regards to #mbsync. so I’m going to try this here, too. Hashtags will be #buildinpublic and #zknstuff.

#buildinpublic #mbsync That’s it for today. Lots of little things done. Learned some stuff and realized a lot of things about how to structure the sync state. This’ll be the most important part of the app, so it’s alright if it takes some time and some attempts to get right.

#buildinpublic #mbsync Alright. The test for a post that is unchanged does the thing and I already created a similar test for a newly created post. However I don’t like how the sync status is saved in the db. Or rather how different kinds of logic are reduced to only the sync state. We need to differentiate between the state of the sync itself (pending/synchronized/error) and what we sync (creation/update/deletion). At least I think it’d be nice to be explicit about it. Makes it more testable. So we need two fields in the db on every post:

  • sync_status: pending, synchronized, error, none
  • sync_intention: create, update, delete, none

#buildinpublic #mbsync So. Me the last few hours:

I’m not distracted, I’m on a side quest. — https://beige.party/@RickiTarr/112264314660393539

Where was I? Oh yes. Refactoring/Writing a test for the markForSync method. I have the starting point for the test, which creates a post using a factory and writes that same data to disk. Now we need to run the post through our method and see that the posts sync_status is not marked for creation/update/deletion.

#buildinpublic #mbsync Ugh. And even more problems. I use the laravel validated dto package, because I like working with DTOs and getting validation “for free” is nice. However:

If a post doesn’t have a category the mb api returns an empty array. Fine. I can validate this as 'categories' => ['present', 'array'],, but interestingly the DTOs saves this internally as null. And guess what? categories in not nullable anymore when I try to save a post with empty categories to the db. Hmpf.

#buildinpublic #mbsync Discoverd another problem: I was allowing the categories field (json in the db) in my Post model to be null. Turns out if you do that you can’t cast that json to array through eloquent. So I changed that in my migrations, but this means that the assumption you can write a post to the db with categories being null doesn’t hold anymore. So more yak shaving…

#buildinpublic #mbsync I’m much more familiar with codeception and my brain is kinda mushy so figuring out how to do things in PHPUnit took its time. However, I refactored that storePost method now and have a test (which I wrote first or at least started to write first) to make sure it does what I want it to do. Now back to testMarkForSync.

#buildinpublic #mbsync After a late combined breakfast/lunch back at it. Still writing the testcase for storePost. I had to think about how to structure tests that rely on test data or rather needs a directory to write to. I generally like to mirror the structure of the app in my tests, so that I can find the things I need easily. I have now introduced a testData directory where I can write to/from the filesystem. It looks like this:

#buildinpublic #mbsync Well, that worked. We just had to add:

public function posts(): \Illuminate\Database\Eloquent\Relations\HasMany
    {
        return $this->hasMany(Post::class);
    }

to the Blog model.

Now we can do the following in our test:

$blog = Blog::factory()->has(Post::factory()->count(1))->create();
$post = $blog->posts->first();

That post now has some fake data as defined in my PostFactory.

Time to write this to disk. We could use PostFilePersistorService::storePost. This method makes a couple of assumptions about where to write data and I would like it to just write where I want it to. So I will refactor the path generation logic out of storePost. In the future it should be handed in via a parameter. This is a big change but we’re the only developer and in the “break lots of things” phase, so I feel this is fine.

There is only one problem: We again have no test! So what I’ll do is make the other test pass, add a TODO to it and create a test for storePost. First things first.

#buildinpublic #mbsync Well, it seems that my models are incomplete. I am missing a relationship between a blog and its posts. This is needed so that my factory can create some posts to test with.

#buildinpublic #mbsync No tests, alright. What do we want to test here? If we call markForSync() a changed post, a deleted post, a new post and an unchanged post should all be detected correctly. So let’s start with the simple case that a post in the db and in the filesystem are the same.

#buildinpublic #mbsync Continuing from yesterday. I will rewrite the markForSync method so it iterates over the posts in the db instead of the files to be able to detect deleted files as well. Just have to check if there are any tests to work with, or if I have to add them first.

#buildinpublic #mbsync Well. Looks like I will not continue today…

#buildinpublic #mbsync Will continue but we have stuff to do in the city.

#buildinpublic #mbsync It seems that I have a column in the posts table called sync_status that is enum('pending','synced','deleted') so it seems to me I need to set this to deleted if the file is missing, which implies that instead of iterating over the blog post files and marking the posts in the db I need to iterate over the blogposts in the db since otherwise I miss the deleted files. Does this make sense?

  • Files:
    • 1
    • 2
    • 3
  • Posts in DB
    • 1
    • 2
    • 3

I was checking the files against the db to find new and updated posts, but I’m now going to check the db against the files instead. I should still be able to find new/updated posts, but also posts that are missing from the filesystem.

#buildinpublic #mbsync Alright found something todo: // TODO: mark post as deleted

The way this app works is by downloading posts and keeping a hash in the database and also saving them to the filesystem. There is a rudimentary implementation for new and updated posts, but files that are deleted from the filesystem are not marked as deleted in the db. The db is what gets synced with mb though. So we need a field on the post that marks it as deleted, so that when we sync the next time the post can be deleted from mb.

One thing that always happens with this one: Don’t know what the next step is. Learning goals are for now:

  • phpstorm
  • laravel
  • tdd

So I use herd and dbngin for the local dev env and use phpstorm as the ide and realize that I have uncomitted stuff. Let’s see if the tests pass.

#buildinpublic #mbsync

Inspired by @vincent I am going to #buildinpublic. I have a nice little learning project that is supposed to be like blot but for micro.blog. I’m calling it mb-sync and I’m going to use #mbsync to refer to it.