| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | package cleaner_test | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							| 
									
										
										
										
											2023-10-21 17:23:05 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/util" | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-21 17:23:05 +02:00
										 |  |  | func copyMap(in map[string]*gtsmodel.Emoji) map[string]*gtsmodel.Emoji { | 
					
						
							|  |  |  | 	out := make(map[string]*gtsmodel.Emoji, len(in)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for k, v1 := range in { | 
					
						
							|  |  |  | 		v2 := new(gtsmodel.Emoji) | 
					
						
							|  |  |  | 		*v2 = *v1 | 
					
						
							|  |  |  | 		out[k] = v2 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return out | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | func (suite *CleanerTestSuite) TestEmojiUncacheRemote() { | 
					
						
							|  |  |  | 	suite.testEmojiUncacheRemote( | 
					
						
							|  |  |  | 		context.Background(), | 
					
						
							|  |  |  | 		mapvals(suite.emojis), | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (suite *CleanerTestSuite) TestEmojiUncacheRemoteDryRun() { | 
					
						
							|  |  |  | 	suite.testEmojiUncacheRemote( | 
					
						
							|  |  |  | 		gtscontext.SetDryRun(context.Background()), | 
					
						
							|  |  |  | 		mapvals(suite.emojis), | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (suite *CleanerTestSuite) TestEmojiFixBroken() { | 
					
						
							|  |  |  | 	suite.testEmojiFixBroken( | 
					
						
							|  |  |  | 		context.Background(), | 
					
						
							|  |  |  | 		mapvals(suite.emojis), | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (suite *CleanerTestSuite) TestEmojiFixBrokenDryRun() { | 
					
						
							|  |  |  | 	suite.testEmojiFixBroken( | 
					
						
							|  |  |  | 		gtscontext.SetDryRun(context.Background()), | 
					
						
							|  |  |  | 		mapvals(suite.emojis), | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (suite *CleanerTestSuite) TestEmojiPruneUnused() { | 
					
						
							|  |  |  | 	suite.testEmojiPruneUnused( | 
					
						
							|  |  |  | 		context.Background(), | 
					
						
							|  |  |  | 		mapvals(suite.emojis), | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (suite *CleanerTestSuite) TestEmojiPruneUnusedDryRun() { | 
					
						
							|  |  |  | 	suite.testEmojiPruneUnused( | 
					
						
							|  |  |  | 		gtscontext.SetDryRun(context.Background()), | 
					
						
							|  |  |  | 		mapvals(suite.emojis), | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (suite *CleanerTestSuite) TestEmojiFixCacheStates() { | 
					
						
							| 
									
										
										
										
											2023-10-21 17:23:05 +02:00
										 |  |  | 	// Copy testrig emojis + mark | 
					
						
							|  |  |  | 	// rainbow emoji as uncached | 
					
						
							|  |  |  | 	// so there's something to fix. | 
					
						
							|  |  |  | 	emojis := copyMap(suite.emojis) | 
					
						
							|  |  |  | 	emojis["rainbow"].Cached = util.Ptr(false) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 	suite.testEmojiFixCacheStates( | 
					
						
							|  |  |  | 		context.Background(), | 
					
						
							| 
									
										
										
										
											2023-10-21 17:23:05 +02:00
										 |  |  | 		mapvals(emojis), | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (suite *CleanerTestSuite) TestEmojiFixCacheStatesDryRun() { | 
					
						
							| 
									
										
										
										
											2023-10-21 17:23:05 +02:00
										 |  |  | 	// Copy testrig emojis + mark | 
					
						
							|  |  |  | 	// rainbow emoji as uncached | 
					
						
							|  |  |  | 	// so there's something to fix. | 
					
						
							|  |  |  | 	emojis := copyMap(suite.emojis) | 
					
						
							|  |  |  | 	emojis["rainbow"].Cached = util.Ptr(false) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 	suite.testEmojiFixCacheStates( | 
					
						
							|  |  |  | 		gtscontext.SetDryRun(context.Background()), | 
					
						
							| 
									
										
										
										
											2023-10-21 17:23:05 +02:00
										 |  |  | 		mapvals(emojis), | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (suite *CleanerTestSuite) testEmojiUncacheRemote(ctx context.Context, emojis []*gtsmodel.Emoji) { | 
					
						
							|  |  |  | 	var uncacheIDs []string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Test state. | 
					
						
							|  |  |  | 	t := suite.T() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get max remote cache days to keep. | 
					
						
							|  |  |  | 	days := config.GetMediaRemoteCacheDays() | 
					
						
							|  |  |  | 	olderThan := time.Now().Add(-24 * time.Hour * time.Duration(days)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, emoji := range emojis { | 
					
						
							|  |  |  | 		// Check whether this emoji should be uncached. | 
					
						
							|  |  |  | 		ok, err := suite.shouldUncacheEmoji(ctx, emoji, olderThan) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("error checking whether emoji should be uncached: %v", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if ok { | 
					
						
							|  |  |  | 			// Mark this emoji ID as to be uncached. | 
					
						
							|  |  |  | 			uncacheIDs = append(uncacheIDs, emoji.ID) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Attempt to uncache remote emojis. | 
					
						
							|  |  |  | 	found, err := suite.cleaner.Emoji().UncacheRemote(ctx, olderThan) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("error uncaching remote emojis: %v", err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check expected were uncached. | 
					
						
							|  |  |  | 	if found != len(uncacheIDs) { | 
					
						
							|  |  |  | 		t.Errorf("expected %d emojis to be uncached, %d were", len(uncacheIDs), found) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if gtscontext.DryRun(ctx) { | 
					
						
							|  |  |  | 		// nothing else to test. | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, id := range uncacheIDs { | 
					
						
							|  |  |  | 		// Fetch the emoji by ID that should now be uncached. | 
					
						
							|  |  |  | 		emoji, err := suite.state.DB.GetEmojiByID(ctx, id) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("error fetching emoji from database: %v", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Check cache state. | 
					
						
							|  |  |  | 		if *emoji.Cached { | 
					
						
							|  |  |  | 			t.Errorf("emoji %s@%s should have been uncached", emoji.Shortcode, emoji.Domain) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Check that the emoji files in storage have been deleted. | 
					
						
							|  |  |  | 		if ok, err := suite.state.Storage.Has(ctx, emoji.ImagePath); err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("error checking storage for emoji: %v", err) | 
					
						
							|  |  |  | 		} else if ok { | 
					
						
							|  |  |  | 			t.Errorf("emoji %s@%s image path should not exist", emoji.Shortcode, emoji.Domain) | 
					
						
							|  |  |  | 		} else if ok, err := suite.state.Storage.Has(ctx, emoji.ImageStaticPath); err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("error checking storage for emoji: %v", err) | 
					
						
							|  |  |  | 		} else if ok { | 
					
						
							|  |  |  | 			t.Errorf("emoji %s@%s image static path should not exist", emoji.Shortcode, emoji.Domain) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (suite *CleanerTestSuite) shouldUncacheEmoji(ctx context.Context, emoji *gtsmodel.Emoji, after time.Time) (bool, error) { | 
					
						
							|  |  |  | 	if emoji.ImageRemoteURL == "" { | 
					
						
							|  |  |  | 		// Local emojis are never uncached. | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if emoji.Cached == nil || !*emoji.Cached { | 
					
						
							|  |  |  | 		// Emoji is already uncached. | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get related accounts using this emoji (if any). | 
					
						
							|  |  |  | 	accounts, err := suite.state.DB.GetAccountsUsingEmoji(ctx, emoji.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return false, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if accounts are recently updated. | 
					
						
							|  |  |  | 	for _, account := range accounts { | 
					
						
							|  |  |  | 		if account.FetchedAt.After(after) { | 
					
						
							|  |  |  | 			return false, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get related statuses using this emoji (if any). | 
					
						
							|  |  |  | 	statuses, err := suite.state.DB.GetStatusesUsingEmoji(ctx, emoji.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return false, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if statuses are recently updated. | 
					
						
							|  |  |  | 	for _, status := range statuses { | 
					
						
							|  |  |  | 		if status.FetchedAt.After(after) { | 
					
						
							|  |  |  | 			return false, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return true, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (suite *CleanerTestSuite) testEmojiFixBroken(ctx context.Context, emojis []*gtsmodel.Emoji) { | 
					
						
							|  |  |  | 	var fixIDs []string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Test state. | 
					
						
							|  |  |  | 	t := suite.T() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, emoji := range emojis { | 
					
						
							|  |  |  | 		// Check whether this emoji should be fixed. | 
					
						
							|  |  |  | 		ok, err := suite.shouldFixBrokenEmoji(ctx, emoji) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("error checking whether emoji should be fixed: %v", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if ok { | 
					
						
							|  |  |  | 			// Mark this emoji ID as to be fixed. | 
					
						
							|  |  |  | 			fixIDs = append(fixIDs, emoji.ID) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Attempt to fix broken emojis. | 
					
						
							|  |  |  | 	found, err := suite.cleaner.Emoji().FixBroken(ctx) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("error fixing broken emojis: %v", err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check expected were fixed. | 
					
						
							|  |  |  | 	if found != len(fixIDs) { | 
					
						
							|  |  |  | 		t.Errorf("expected %d emojis to be fixed, %d were", len(fixIDs), found) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if gtscontext.DryRun(ctx) { | 
					
						
							|  |  |  | 		// nothing else to test. | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, id := range fixIDs { | 
					
						
							|  |  |  | 		// Fetch the emoji by ID that should now be fixed. | 
					
						
							|  |  |  | 		emoji, err := suite.state.DB.GetEmojiByID(ctx, id) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("error fetching emoji from database: %v", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Ensure category was cleared. | 
					
						
							|  |  |  | 		if emoji.CategoryID != "" { | 
					
						
							|  |  |  | 			t.Errorf("emoji %s@%s should have empty category", emoji.Shortcode, emoji.Domain) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (suite *CleanerTestSuite) shouldFixBrokenEmoji(ctx context.Context, emoji *gtsmodel.Emoji) (bool, error) { | 
					
						
							|  |  |  | 	if emoji.CategoryID == "" { | 
					
						
							|  |  |  | 		// no category issue. | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get the related category for this emoji. | 
					
						
							|  |  |  | 	category, err := suite.state.DB.GetEmojiCategory(ctx, emoji.CategoryID) | 
					
						
							|  |  |  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return (category == nil), nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (suite *CleanerTestSuite) testEmojiPruneUnused(ctx context.Context, emojis []*gtsmodel.Emoji) { | 
					
						
							|  |  |  | 	var pruneIDs []string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Test state. | 
					
						
							|  |  |  | 	t := suite.T() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, emoji := range emojis { | 
					
						
							|  |  |  | 		// Check whether this emoji should be pruned. | 
					
						
							|  |  |  | 		ok, err := suite.shouldPruneEmoji(ctx, emoji) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("error checking whether emoji should be pruned: %v", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if ok { | 
					
						
							|  |  |  | 			// Mark this emoji ID as to be pruned. | 
					
						
							|  |  |  | 			pruneIDs = append(pruneIDs, emoji.ID) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Attempt to prune emojis. | 
					
						
							|  |  |  | 	found, err := suite.cleaner.Emoji().PruneUnused(ctx) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("error fixing broken emojis: %v", err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check expected were pruned. | 
					
						
							|  |  |  | 	if found != len(pruneIDs) { | 
					
						
							|  |  |  | 		t.Errorf("expected %d emojis to be pruned, %d were", len(pruneIDs), found) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if gtscontext.DryRun(ctx) { | 
					
						
							|  |  |  | 		// nothing else to test. | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, id := range pruneIDs { | 
					
						
							|  |  |  | 		// Fetch the emoji by ID that should now be pruned. | 
					
						
							|  |  |  | 		emoji, err := suite.state.DB.GetEmojiByID(ctx, id) | 
					
						
							|  |  |  | 		if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 			t.Fatalf("error fetching emoji from database: %v", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Ensure gone. | 
					
						
							|  |  |  | 		if emoji != nil { | 
					
						
							|  |  |  | 			t.Errorf("emoji %s@%s should have been pruned", emoji.Shortcode, emoji.Domain) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (suite *CleanerTestSuite) shouldPruneEmoji(ctx context.Context, emoji *gtsmodel.Emoji) (bool, error) { | 
					
						
							|  |  |  | 	if emoji.ImageRemoteURL == "" { | 
					
						
							|  |  |  | 		// Local emojis are never pruned. | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get related accounts using this emoji (if any). | 
					
						
							|  |  |  | 	accounts, err := suite.state.DB.GetAccountsUsingEmoji(ctx, emoji.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return false, err | 
					
						
							|  |  |  | 	} else if len(accounts) > 0 { | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get related statuses using this emoji (if any). | 
					
						
							|  |  |  | 	statuses, err := suite.state.DB.GetStatusesUsingEmoji(ctx, emoji.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return false, err | 
					
						
							|  |  |  | 	} else if len(statuses) > 0 { | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return true, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (suite *CleanerTestSuite) testEmojiFixCacheStates(ctx context.Context, emojis []*gtsmodel.Emoji) { | 
					
						
							|  |  |  | 	var fixIDs []string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Test state. | 
					
						
							|  |  |  | 	t := suite.T() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, emoji := range emojis { | 
					
						
							|  |  |  | 		// Check whether this emoji should be fixed. | 
					
						
							|  |  |  | 		ok, err := suite.shouldFixEmojiCacheState(ctx, emoji) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("error checking whether emoji should be fixed: %v", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if ok { | 
					
						
							|  |  |  | 			// Mark this emoji ID as to be fixed. | 
					
						
							|  |  |  | 			fixIDs = append(fixIDs, emoji.ID) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Attempt to fix broken emoji cache states. | 
					
						
							|  |  |  | 	found, err := suite.cleaner.Emoji().FixCacheStates(ctx) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("error fixing broken emojis: %v", err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check expected were fixed. | 
					
						
							|  |  |  | 	if found != len(fixIDs) { | 
					
						
							|  |  |  | 		t.Errorf("expected %d emojis to be fixed, %d were", len(fixIDs), found) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if gtscontext.DryRun(ctx) { | 
					
						
							|  |  |  | 		// nothing else to test. | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, id := range fixIDs { | 
					
						
							|  |  |  | 		// Fetch the emoji by ID that should now be fixed. | 
					
						
							|  |  |  | 		emoji, err := suite.state.DB.GetEmojiByID(ctx, id) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("error fetching emoji from database: %v", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Ensure emoji cache state has been fixed. | 
					
						
							|  |  |  | 		ok, err := suite.shouldFixEmojiCacheState(ctx, emoji) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("error checking whether emoji should be fixed: %v", err) | 
					
						
							|  |  |  | 		} else if ok { | 
					
						
							|  |  |  | 			t.Errorf("emoji %s@%s cache state should have been fixed", emoji.Shortcode, emoji.Domain) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (suite *CleanerTestSuite) shouldFixEmojiCacheState(ctx context.Context, emoji *gtsmodel.Emoji) (bool, error) { | 
					
						
							|  |  |  | 	// Check whether emoji image path exists. | 
					
						
							|  |  |  | 	haveImage, err := suite.state.Storage.Has(ctx, emoji.ImagePath) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return false, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check whether emoji static path exists. | 
					
						
							|  |  |  | 	haveStatic, err := suite.state.Storage.Has(ctx, emoji.ImageStaticPath) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return false, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch exists := (haveImage && haveStatic); { | 
					
						
							|  |  |  | 	case emoji.Cached != nil && | 
					
						
							|  |  |  | 		*emoji.Cached && !exists: | 
					
						
							|  |  |  | 		// (cached can be nil in tests) | 
					
						
							|  |  |  | 		// Cached but missing files. | 
					
						
							|  |  |  | 		return true, nil | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case emoji.Cached != nil && | 
					
						
							|  |  |  | 		!*emoji.Cached && exists: | 
					
						
							|  |  |  | 		// (cached can be nil in tests) | 
					
						
							|  |  |  | 		// Uncached but unexpected files. | 
					
						
							|  |  |  | 		return true, nil | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		// No cache state issue. | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |